如何在后台继续执行任务、等待和异步

How to proceed with Task, await and async in background

我面临需要帮助以了解如何正确进行的情况。

我有一个行动是一种预算形式。用户填写完所有字段后,用户将保存预算并按下“发送”按钮。

发送按钮将保存数据,然后向所有公司发送电子邮件。当我说所有公司时,我指的是大约 1000 家公司。

但是,问题是当我按下发送按钮时,在为公司发送电子邮件时页面被锁定,在向所有公司发送电子邮件后,网站 return查看。

我想做的是,当用户按下“发送”按钮时,网站会节省预算,开始在后台发送电子邮件,并且几乎立即 return 向用户显示视图,但是, 与此同时,该网站正在后台向 1000 家公司发送电子邮件。

我是怎么做到的?这是我的方法的签名:

public async Task<PartialViewResult> EnviarProposta(PostOrcamentoServicoProposta proposta)
{
    // persist the budget
    SaveData(proposta);

    // get all companies...
    var companies = getCompanies(proposta);

    foreach (var company in companies)
        await EmailFactory.SendBudget(proposta, company).SendAsync();

    return PartialView(proposta);
}

SendAsync 是 SMTP 的异步方法。

问题是,我如何简单地将发送电子邮件方法扔到后台和 return 视图,而不等待发送完成?

第二个问题是我正在使用一个框架来转换电子邮件中的视图,因此,为此我们需要控制器的上下文。

我应该使用 task.factory.startnew 吗?

我应该使用 Thread 吗?

考虑因素: 不幸的是,异步控制器操作在这种情况下没有帮助,因为它们在等待异步操作完成时不会对用户产生响应。他们只解决与线程池和应用程序容量相关的内部问题。

如果您需要 "Fire and Forget" 方法,请考虑使用 Task.Factory.StartNew()。

Task.Factory.StartNew( () => 
{
    foreach(Company c in companies)
    {
        // send your email here, make sure you have good error handling!
        EmailFactory.SendBudget(proposta)
    }
});

有很多方法可以做到。例如,您可以简单地从上面的代码中删除 await 。或者您可以使用 LINQ 并让您的任务数组使用延续。

Task[] tasks = companies.Select(c => EmailFactory.SendBudget(proposta).SendAsync(c)).ToArray();
Task.Factory.ContinueWhenAll(tasks, t => { /*do something*/ });

(我假设您需要以某种方式将公司传递给每个 Send 方法调用)

或者如 Ahmed 所述,您可以启动一个线程并在其中发送电子邮件,以这种或其他方式。

由于您是通过 asp.net 托管的,正确的方法有点复杂。

通过线程排队工作不会通知 运行time 有工作要完成。这意味着当应用程序域被回收时(这是在没有您干预的情况下不时完成的),您排队的工作不能保证完成。如果这与您无关,请继续排队后台任务。

"proper" 方法是以某种方式坚持该工作(消息队列、sql 服务器等...)并让另一个主机 运行 它。

查看此 blog post 以获得更多 details/possible 解决方案。

我喜欢将需要发送的电子邮件排队,并让另一个服务实际执行发送的想法...

...特别是因为似乎每封电子邮件都在完成一些工作。

例如,您可以只在数据库中创建一个条目,其他服务从中工作,并将它们标记为已发送。如果您的电子邮件的内容实际上是通过调用视图生成的,那么单独的服务应该仍然能够通过 WebRequest 或类似的方式简单地调用相应的操作。

然后您还可以将工作量标记为已完成,并为该事实提供证据。