做异步工作的最佳方法是返回快速响应

时间:2018-02-02 15:00:07

标签: c# asp.net .net asp.net-web-api async-await

我正在使用ASP.NET上的REST API。我想知道哪个是独立于将响应发送到客户端的后台工作的最佳方式。基本上,我有一个PUT方法,我必须发送几封电子邮件。假设他们是10封电子邮件。我将恢复所有方法,使一切更清晰:

我尝试了以下方法,并且在发送电子邮件之后发送了响应的两种方式,并且它们花了相同的时间:

方式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");
            }
        });
    }

方式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");
            }
        });
    }

将响应发送给客户端的最佳方法是什么,而不必等待所有电子邮件都已发送?我必须使用ThreadPool吗?

2 个答案:

答案 0 :(得分:4)

我会更进一步解耦并使用类似于域事件的通用事件。

如果控制器可以完成其工作并返回响应,无论电子邮件是否已发送,强烈建议发送电子邮件超出控制器的责任范围。 (特别是通过电子邮件,控制器无法确定是否已发送电子邮件。)

在那种情况下,我会使用某种事件总线。一些示例使用静态实例,或者它可能是您注入控制器的内容。

然后,控制器不是发送电子邮件,而是提出一个事件,比如

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

然后你有一个事件处理程序(你甚至可以有多个),一个事件处理程序发送电子邮件,包括使它成为async并处理任何异常。

这使得单元测试变得更加容易。您无需测试控制器是否已发送电子邮件。您可以模拟事件总线并测试事件是否已引发。然后可以单独测试事件处理程序。

Here is an article on domain events在很长一段时间内引用了很多。我确信那里有更多的最新信息。

有关这些事件的大量信息是在域驱动开发的背景下,但无论您是否在进行DDD,原则和机制都适用。您的类与其方法产生的其他东西分离,但它本身不应该知道或关心 - 比如返回响应的控制器,副作用是发送电子邮件。

Here's an event bus implementation我做了一会儿。我认为我不包括异步处理,但是当你想要发射并忘记时,这可能是有意义的。这是我应该重新审视的事情。

正如评论中所提到的那样,“即发即忘”并不意味着事件的结果是微不足道的。为了确保发生某些事情,事件处理程序将请求放入队列而不是自己完成工作可能是有意义的。这使得它更耐用,因此您可以确保处理队列中的任何内容都得到处理。

答案 1 :(得分:1)

如果您不想等待发送电子邮件(或任何相关错误),您应该进行“开火”并忘记&#34;。解决方案的稳健性:

例如:

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