[英]Safe way to implement a “Fire and Forget” method on ASP.NET Core
我正在尝试实现一个简单的日志库,它将在多个项目中使用。 库的工作是将HTTP请求发送到ElasticSearch。 这个库的要点是它不能等待响应。 另外,我不关心任何错误/异常。 它必须将请求发送到ElasticSearch,并立即返回。 我不想使用返回类型Task
创建接口,我希望它们保持void
。
以下是我的示例代码。 这是“火与忘”的正确和安全的实施吗? 如果我在高负载库中使用Task.Run()
,这样可以吗? 或者我应该避免在我的情况下使用Task.Run()
? 另外,如果我不使用await
与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的Startup类中的ConfigureServices方法中注册logger的方法:
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
}
Re:对async方法的异步调用与Task.Run()
由于是在限制的工作的CPU只有少量的Post
(即创建JSON有效载荷),没有其他的好处Task.Run
-调度,对线程池一个新的任务将超过任何好处IMO的开销。 即
Post(action, message, LogLevel.Info);*/ // Or should I just use it like this?
两种方法中哪一种更好。 您可能希望禁止在未单独的任务中关联的编译器警告,并为下一个开发人员留下代码的注释。
但根据Stephen Cleary的确切答案, ASP.Net中的火灾和遗忘几乎从来都不是一个好主意 。 优选的是将工作(例如通过队列)卸载到Windows服务,Azure Web Job等。
还有其他危险 - 如果unawaited Task抛出,你会想要观察异常 。
此外,请注意在Post
之后完成的任何工作(例如,如果您使用response
),这仍然是一个需要在Threadpool上安排的延续任务 - 如果您启动大量的Post
方法,您将结束完成时有很多线程争用。
Re:另外,如果我不使用await与Task.Run(),我会阻止线程吗?
await
不需要线程 。 await
是语法糖,要求编译器异步重写代码。 Task.Run()
将在ThreadPool上安排第二个任务,它只会在遇到PostAsync
方法之前做很少的工作,这就是建议不使用它的原因。
从Info
到Post
的未调用调用上的调用者线程使用/阻塞量取决于在返回Task
之前完成的Task
类型。 在你的情况下,Json序列化工作将在调用者的线程上完成(我标记为#1),但是与HTTP调用持续时间相比,执行时间应该可以忽略不计。 因此,虽然方法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
}
回复:异常处理
try..catch
块使用异步代码 - await
将检查出错的Task
并引发异常:
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);
}
}
虽然上面的内容符合观察异常的标准,但在全局级别注册UnobservedTaskException
处理程序仍然是个好主意。
这将帮助您检测并识别您未能观察到异常的位置:
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收集任务时才会触发上述处理程序,这可能是在发生异常之后的某个时间。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.