我正在尝试实现一个简单的日志记录库,该库将用于多个项目。库的工作是向 ElasticSearch 发送 HTTP 请求。此库的要点是它不能等待响应。另外,我不关心任何错误/异常。它必须将请求发送到 ElasticSearch,并立即返回。我不想制作带有返回类型 Task
的接口,我希望它们保持无效
。
下面是我的示例代码。是不是正确安全的实现了“火了就忘”?我可以使用<代码>任务吗?在高负载库中运行()?或者我应该避免使用<代码>任务。在我的情况下运行()?此外,如果我不使用< code>await与< code>Task。Run(),我会阻塞线程吗?该代码在库中:
public enum LogLevel
{
Trace = 1,
Debug = 2,
Info = 3,
Warn = 4,
Error = 5,
Fatal = 6
}
public interface ILogger
{
void Info(string action, string message);
}
public class Logger : ILogger
{
private static readonly HttpClient _httpClient = new HttpClient(new HttpClientHandler { Proxy = null, UseProxy = false });
private static IConfigurationRoot _configuration;
public Logger(IConfigurationRoot configuration)
{
_configuration = configuration;
}
public void Info(string action, string message)
{
Task.Run(() => Post(action, message, LogLevel.Info));
/*Post(action, message, LogLevel.Info);*/ // Or should I just use it like this?
}
private async Task Post(string action, string message, LogLevel logLevel)
{
// Here I have some logic
var jsonData = JsonConvert.SerializeObject(log);
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(_configuration.GetValue<string>("ElasticLogger:Url"), content);
// No work here, the end of the method
}
}
这是我如何在 Web API 的启动类中的配置服务方法中注册记录器的方式:
public void ConfigureServices(IServiceCollection services)
{
// ......
services.AddSingleton<ILogger, Logger>();
// .....
}
这段代码在我的web api的一个方法中:
public void ExecuteOperation(ExecOperationRequest request)
{
// Here some business logic
_logger.Info("ExecuteOperation", "START"); // Log
// Here also some business logic
_logger.Info("ExecuteOperation", "END"); // Log
}
回复:对异步方法的未等待调用vs Task.Run()
由于Post
中只有少量CPU绑定工作(即创建json有效负载),因此没有另一个任务的好处。运行
-在线程池上调度新任务的开销将超过IMO的任何好处。即。
Post(action, message, LogLevel.Info);*/ // Or should I just use it like this?
是两种方法中较好的一种。您可能希望隐藏与未等待的任务相关的编译器警告,并留下注释,以便下一个开发人员看到代码。
但根据斯蒂芬·克利利(Stephen Cleary)的明确答案,ASP.Net 中解雇和遗忘几乎从来都不是一个好主意。最好是将工作(例如通过队列)卸载到Windows服务,Azure Web Job等。
还有额外的危险——如果未等待的Task抛出,您将需要观察异常。
此外,请注意,在Post
之后完成的任何工作(例如,如果您使用响应
),这仍然是一个需要在Threadool上安排的延续任务-如果您触发大量的Post
方法,当它们完成时,您将最终导致大量线程争用。
回复:同样,如果我不使用任务等待。Run(),我会阻塞线程吗?
await
不需要线程await
是一种语法糖,它要求编译器异步重写代码Task.Run()
将在ThreadPool上调度第二个任务,该任务在到达PostAsync
方法之前只会做少量工作,这就是为什么建议不要使用它的原因。
从< code>Info到< code>Post的未等待调用的调用方线程使用量/阻塞量取决于在返回< code>Task之前完成的工作类型。在您的情况下,Json序列化工作将在调用者的线程上完成(我已经标记为#1),但是与HTTP调用持续时间相比,执行时间应该可以忽略不计。因此,尽管没有被方法< code>Info等待,但是当Http调用完成时,HTTP调用之后的任何代码仍然需要被调度,并且将在任何可用的线程(#2)上被调度。
public void Info(string action, string message)
{
#pragma warning disable 4014 // Deliberate fire and forget
Post(action, message, LogLevel.Info); // Unawaited Task, thread #1
#pragma warning restore 4014
}
private async Task Post(string action, string message, LogLevel logLevel)
{
var jsonData = JsonConvert.SerializeObject(log); // #1
var content = new StringContent(jsonData, Encoding.UTF8, "application/json"); // #1
var response = await httpClient.PostAsync(...), content);
// Work here will be scheduled on any available Thread, after PostAsync completes #2
}
回复:异常处理
尝试。。catch
块使用异步代码 - await
将检查出错的任务
并引发异常:
public async Task Post()
{
try
{
// ... other serialization code here ...
await HttpPostAsync();
}
catch (Exception ex)
{
// Do you have a logger of last resort?
Trace.WriteLine(ex.Message);
}
}
尽管上述将满足观察异常的标准,但在全局级别注册UnobservedTaskExc的
处理程序仍然是一个好主意。
这将帮助您检测和识别您未能观察到异常的地方:
TaskScheduler.UnobservedTaskException += (sender, eventArgs) =>
{
eventArgs.SetObserved();
((AggregateException)eventArgs.Exception).Handle(ex =>
{
// Arriving here is BAD - means we've forgotten an exception handler around await
// Or haven't checked for `.IsFaulted` on `.ContinueWith`
Trace.WriteLine($"Unobserved Exception {ex.Message}");
return true;
});
};
请注意,上述处理程序仅在 GC 收集任务时触发,这可能是异常发生后的一段时间。