实施“解雇"的安全方法ASP.NET Core上的方法 [英] Safe way to implement a "Fire and Forget" method on ASP.NET Core

查看:77
本文介绍了实施“解雇"的安全方法ASP.NET Core上的方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试实现一个简单的日志记录库,该库将在多个项目中使用.库的工作是将HTTP请求发送到ElasticSearch.该库的要点是它不能等待响应.另外,我不在乎任何错误/异常.它必须将请求发送到ElasticSearch,并立即返回.我不想使用返回类型为Task的接口,而是希望它们保持void的状态.

I am trying to implement a simple logging library which will be used across multiple projects. The job of library is to send HTTP requests to ElasticSearch. The main point of this library is that it must not wait for the response. Also, I don't care about any error/exceptions. It must send the request to ElasticSearch, and immediately return. I don't want to make interfaces with return type Task, I want them to stay void.

下面是我的示例代码.这是解雇"的正确和安全实施吗?如果在高负载库中使用Task.Run()可以吗?还是应该避免在我的情况下使用Task.Run()?另外,如果我不将awaitTask.Run()一起使用,是否会阻塞线程? 这段代码在库中:

Below is my sample code. Is it a correct and safe implementation of "Fire and Forget"? Is it ok if I use Task.Run() in a high load library? Or should I avoid using Task.Run() in my case? Also, if I don't use await with Task.Run(), will I block the thread? This code is in the library:

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方法中注册记录器的方式:

This is how I register logger inside ConfigureServices method in Startup class of my web api:

public void ConfigureServices(IServiceCollection services)
{
     // ......

     services.AddSingleton<ILogger, Logger>();

     // .....
}

此代码在我的网络api中的方法中

This code is in a method inside my 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:未等待的异步方法与Task.Run()的调用

由于Post中只有少量CPU绑定工作(即创建json有效负载),因此没有另一个Task.Run的好处-在线程池上安排新Task的开销将超过IMO的任何好处.即

Since there's only a small amount of CPU bound work in Post (i.e. creating json payload), there's no benefit of another Task.Run - the overhead of scheduling a new Task on the Threadpool will outweigh any benefit IMO. i.e.

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

是两种方法中较好的一种.您可能希望隐藏未等待的Task中相关的编译器警告,并为下一个开发人员在代码中留下注释.

is the better of the two approaches. You'll likely want to suppress the compiler warning associated within unawaited Tasks and leave a comment for the next dev to come across the code.

但是,根据Stephen Cleary的明确答案,在 ASP.Net中绝不会是一个好主意.最好是分担工作,例如通过队列连接到Windows Service,Azure Web Job等.

But as per Stephen Cleary's definitive answer, fire and forget in ASP.Net is almost never a good idea. Preferable would be to offload work, e.g. via a queue, to a Windows Service, Azure Web Job et al.

还有其他危险-如果引发未等待的任务,您将遵守该例外情况.

There are additional dangers - if the unawaited Task throws, you'll want to observe the exception.

此外,请注意,在Post之后所做的任何工作(例如,如果您使用response)仍然是一项延续任务,需要在线程池上安排-如果您释放了大量Post方法,当它们完成时,您会遇到很多线程争用.

Also, note that any work done after the Post (e.g. if you work with response) that this is still a continuation Task which needs to be scheduled on the Threadpool - if you fire off high volumes of your Post method, you'll wind up with a lot of thread contention when they complete.

回复:另外,如果我不对Task.Run()使用await,我是否会阻塞线程?

await 不需要线程. await是语法糖,要求编译器异步重写您的代码. Task.Run()将在ThreadPool上安排第二个任务,该任务只会在执行PostAsync方法之前执行少量工作,这就是为什么建议不要使用它的原因.

await doesn't require a thread. await is syntactic sugar to ask the compiler to rewrite your code asynchronously. Task.Run() will schedule a second task on the ThreadPool, which will only do a tiny amount of work before it hits the PostAsync method, which is why the recommendation is not to use it.

在未等待的从InfoPost的调用中,调用方线程使用量/块的数量取决于返回Task之前执行的工作类型. 在您的情况下,Json序列化工作将在调用者的线程上完成(我已标记为#1),但是与HTTP调用持续时间相比,执行时间应该可以忽略不计.因此,尽管方法Info没有等待,但是HTTP调用完成后,仍然需要对HTTP调用之后的任何代码进行调度,并且将在任何可用线程(#2)上对其进行调度.

The amount of caller thread usage/block on the unawaited call from Info to Post depends on what kind of work is done before the Task is returned. In your case the Json serialization work would be done on the caller's thread (I've labelled #1), however the execution time should be negligible in comparison to the HTTP call duration. So although not awaited by method Info, any code after the HTTP call will still need to be scheduled when the Http call completes, and will be scheduled on any available thread (#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 并引发例外:

try..catch blocks work with async code - await will check for a faulted Task and raise an exception:

 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处理程序仍然是一个好主意.

Although the above will meet the criteria for observing the exception, it is still a good idea to register an UnobservedTaskException handler at the global level.

这将帮助您检测和识别未能观察到异常的位置:

This will help you detect and identify where you've failed to observe an exception:

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收集了Task时才会触发上述处理程序,这可能是在异常发生后的一段时间.

Note that the above handler is only triggered when the Task is collected by the GC, which can be some time after the occurrence of the exception.

这篇关于实施“解雇"的安全方法ASP.NET Core上的方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆