简体   繁体   English

进行异步工作以返回快速响应的最佳方法

[英]Best way to do async work in order to return fast response

I'm working on a REST API on ASP.NET. 我正在使用ASP.NET上的REST API。 I want to know which is the best way to do a background work indepentently of sending the response to the client. 我想知道哪种是独立于将响应发送给客户端的后台工作的最佳方法。 Basically, I have a PUT method where I have to send several emails. 基本上,我有一种PUT方法,必须发送几封电子邮件。 Suppose they are 10 emails. 假设它们是10封电子邮件。 I will resume all the methods to make everything more clear: 我将恢复所有方法以使所有内容更加清晰:

I tried the following and both ways the response was sent after the emails were sent and they took the same time: 我尝试了以下方法,并且在发送电子邮件之后都用了两种方式发送响应,并且花费了相同的时间:

Way 1: 方法1:

    [EnableCors(origins: "*", headers: "*", methods: "*", SupportsCredentials = true)]
    public async Task<HttpResponseMessage> Put(string id, [FromBody]InformeModel informe)
    {
        if (CookieManager.ValidarCookie(Request.Headers.GetCookies("mb-session").FirstOrDefault()) == EstadoCookie.VALIDA)
        {
            await SendMails()

            return Request.CreateResponse(HttpStatusCode.OK);
        }
        else
        {
            return Request.CreateResponse(HttpStatusCode.Unauthorized);
        }
    }


    private async Task SendMails()
    {
        await Task.Run(() => {
            foreach (string m in mails)
            {
                Mail mail = new Mail("test@test.com", 25, "test@test.com.ar", "myPass");
                mail.SendMailHTML(m, "Email Title", "This is the Email");
            }
        });
    }

Way 2: 方式2:

    // PUT: api/Informes/5
    [EnableCors(origins: "*", headers: "*", methods: "*", SupportsCredentials = true)]
    public HttpResponseMessage Put(string id, [FromBody]InformeModel informe)
    {
        if (CookieManager.ValidarCookie(Request.Headers.GetCookies("mb-session").FirstOrDefault()) == EstadoCookie.VALIDA)
        {
            SendMails()

            return Request.CreateResponse(HttpStatusCode.OK);
        }
        else
        {
            return Request.CreateResponse(HttpStatusCode.Unauthorized);
        }
    }


    private async void SendMails()
    {
        await Task.Run(() => {
            foreach (string m in mails)
            {
                Mail mail = new Mail("test@test.com", 25, "test@test.com.ar", "myPass");
                mail.SendMailHTML(m, "Email Title", "This is the Email");
            }
        });
    }

Which is the best approach in order to send the response to the client and not have to wait that all emails have been sent?. 为了将响应发送到客户端,而不必等待所有电子邮件都已发送,哪种方法最好? Do I have to go with ThreadPool? 我必须使用ThreadPool吗?

I'd go a step further with the decoupling and use a generic event, similar to a domain event. 我将进一步进行解耦,并使用与域事件类似的通用事件。

If the controller can do its job and return a response whether or not the email has been sent, that strongly suggests that sending an email is way outside the responsibility of the controller. 如果控制器可以执行其工作并返回响应(无论是否已发送电子邮件),则强烈建议发送电子邮件超出了控制器的职责范围。 (With emails in particular, the controller can never know for sure whether an email has been sent anyway.) (特别是对于电子邮件,控制器永远无法确定是否已经发送了电子邮件。)

In that scenario I'd use some sort of an event bus. 在那种情况下,我将使用某种事件总线。 Some examples use a static instance, or it could be something that you inject into the controller. 一些示例使用静态实例,或者它可能是您注入到控制器中的东西。

Then, instead of sending an email, the controller just raises an event, like 然后,控制器不发送电子邮件,而是引发一个事件,例如

_eventBus.Raise(new SomethingHappenedEvent(argsThatIncludeRelevantInfo args));

Then you have an event handler (you could even have multiple) and an event handler sends the email, including making it async and handling any exceptions. 然后,您有一个事件处理程序(甚至可能有多个),然后一个事件处理程序发送电子邮件,包括使其async并处理所有异常。

That makes unit testing much easier. 这使得单元测试更加容易。 You don't need to test whether your controller has sent an email. 您无需测试您的控制器是否已发送电子邮件。 You can mock the event bus and just test that an event was raised. 您可以模拟事件总线,然后测试是否引发了事件。 Then the event handlers can be tested separately. 然后可以分别测试事件处理程序。

Here is an article on domain events that was referenced a lot for a while. 这是一篇有关域事件的文章,已有一段时间被引用很多。 I'm sure that there's more up-to-date information out there. 我敢肯定,那里有更多的最新信息。

A lot of information about these events is in the context of Domain Driven Development, but the principles and mechanics apply whether or not you're doing DDD. 有关这些事件的很多信息是在域驱动开发的上下文中进行的,但是无论您是否执行DDD,原理和机制都适用。 Your class becomes decoupled from other things that result from its methods, but which it itself shouldn't know or care about - like a controller that returns a response, and a side effect is sending an email. 您的类与其他方法所产生的其他事物脱钩了,但是它本身却不知道或不关心它们-例如返回响应的控制器,并且副作用是发送电子邮件。

Here's an event bus implementation I did a while back. 这是我前一段时间做的事件总线实现 I don't think I included async handling, but that might make sense when you want to fire-and-forget. 我认为我没有包括异步处理,但是当您要“即发即弃”时,这可能很有意义。 That's something I should revisit. 我应该重新考虑一下。

And as mentioned in a comment, fire-and-forget doesn't mean that the result of the event is trivial. 正如评论中提到的,“一劳永逸”并不意味着事件的结果是微不足道的。 To make sure that something happens it might make sense that the event handler puts a request in a queue instead of doing the work itself. 为了确保某些事情发生,事件处理程序将请求放入队列而不是自己进行工作可能很有意义。 That makes it more durable so you can make sure that whatever gets put in the queue gets processed. 这使其更耐用,因此您可以确保处理放入队列中的所有内容。

If you do not want to wait for emails to be sent (or any related errors), you should do a "fire and forget". 如果您不想等待发送电子邮件(或任何相关错误),则应执行“即发即弃”操作。 Solutions in order of robustness: 按健壮性排序的解决方案:

Ex: 例如:

Task.Run(async () => await ..send emails.)
    .ContinueWith(t => logError(t.Exception.GetBaseException()),
                  TaskContinuationOptions.OnlyOnFaulted);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM