安全实施" Fire and Forget" ASP.NET核心的方法

时间:2018-06-08 08:22:41

标签: c# asynchronous asp.net-core async-await task

我正在尝试实现一个简单的日志库,它将在多个项目中使用。库的工作是将HTTP请求发送到ElasticSearch。这个库的要点是它不能等待响应。另外,我不关心任何错误/异常。它必须将请求发送到ElasticSearch,并立即返回。我不希望使用返回类型Task创建接口,我希望它们保留void

以下是我的示例代码。这是" Fire and Forget"的正确和安全的实施?如果我在高负载库中使用Task.Run(),这样可以吗?或者我应该避免在我的情况下使用Task.Run()?另外,如果我不将awaitTask.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
}

1 个答案:

答案 0 :(得分:3)

Re:对async方法的无法激活调用与Task.Run()

由于Post中只有少量的CPU绑定工作(即创建json有效负载),所以没有其他Task.Run的好处 - 在Threadpool上调度新任务的开销将超过任何有利于IMO。即

Post(action, message, LogLevel.Info);*/ // Or should I just use it like this?

是两种方法中较好的一种。您可能希望禁止在未单独执行的任务中关联的编译器警告,并为下一个开发人员留下注释以获得代码。

但是根据斯蒂芬·克利里的确切答案,在ASP.Net is almost never a good idea中开火并忘记了。优选的是卸载工作,例如,通过队列,到Windows服务,Azure Web Job等。

还有其他危险 - 如果未经任命的任务抛出,您将需要observe the exception

另外,请注意在Post之后完成的任何工作(例如,如果你使用response)这仍然是一个需要在Threadpool上安排的延续任务 - 如果你开火了你的Post方法的数量,完成后你会遇到很多线程争用。

Re:另外,如果我不使用await与Task.Run(),我会阻止线程吗?

await doesn't require a threadawait是语法糖,要求编译器异步重写代码。 Task.Run()将在ThreadPool上安排第二个任务,它只会在它到达PostAsync方法之前做很少的工作,这就是建议不使用它的原因。

InfoPost的未通话呼叫上的呼叫者线程使用/阻止量取决于在返回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
}

Re:异常处理

try..catch阻止使用异步代码 - awaitcheck for a faulted 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收集任务时才会触发上述处理程序,这可能是在发生异常后的一段时间。