【问题标题】:How to proceed with Task, await and async in background如何在后台继续执行任务、等待和异步
【发布时间】:2015-10-01 21:19:00
【问题描述】:

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

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

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

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

我想做的是,当用户按下发送按钮时,网站会节省预算,开始在后台发送电子邮件,几乎立即将视图返回给用户,然而,网站正在后台向 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 的异步方法。

问题是,我如何简单地将发送电子邮件方法扔到后台并返回视图,而无需等待发送完成?

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

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

我应该使用线程吗?

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

【问题讨论】:

  • 基本上你需要启动一个线程并在该线程上执行电子邮件的发送,这样它就可以从为请求服务的当前线程中解脱出来

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


【解决方案1】:

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

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

执行此操作的“正确”方法是以某种方式(消息队列、sql 服务器等)保持该工作并让另一台主机运行它。

有关更多详细信息/可能的解决方案,请参阅此blog post

【讨论】:

  • 有趣的见解是,如果发生应用程序回收,则不能保证在 ASP.Net 环境中完成长时间运行的工作。 blogs.msdn.com/b/webdev/archive/2014/06/04/… 建议的最小条目替代方法之一是 HostingEnvironment.QueueBackgroundWorkItem(ct => SendMailAsync(user.Email));
【解决方案2】:

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

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

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

然后您还可以将工作量标记为已完成,并有证据证明这一事实。

【讨论】:

  • 我也喜欢排队需要发送的邮件的想法,我相信这是大规模的最正确的方式,比如这个案例。没有奇迹可以做,然而,虽然这,我需要做一个解决方法。
【解决方案3】:

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

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

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

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

【讨论】:

    【解决方案4】:

    如果您需要“一劳永逸”的方法,请考虑使用 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)
        }
    });
    

    【讨论】:

    • 作为对 OP 的说明,因为正如您所提到的,这是“即发即弃”,因此,例如,如果所有 1000 封电子邮件都失败,则 UI 不会显示任何错误。
    • 没有指示@dman2306 没有问题,因为我有一个表可以在出现问题时进行注册。问题是我需要控制器上下文,因为 EmailFactory 使用一个框架来转换电子邮件中的视图(使用上下文)。
    • 你需要从控制器的上下文中得到什么?
    • @Greg,上下文用于呈现电子邮件。我正在使用一个框架来转换电子邮件中的视图,因此,为此我需要上下文。
    • 为什么要投反对票?记录/错误处理应该是电子邮件引擎而不是控制器的责任。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-02
    相关资源
    最近更新 更多