后台进程中的SendEmailAsync

时间:2016-03-28 15:18:56

标签: asp.net-mvc async-await background-process

MVC项目附带部分内置电子邮件服务。我已经实现了正常工作的EmailService类。但是,我需要批量发送电子邮件给多个用户,因此我想在后台运行该过程,这就是我被困的地方。我尝试过以下代码:

//To simplify the example, I will just use the loop
for (int i = 1; i <= 10; i++)
{
    //Version 1:
    await UserManager.SendEmailAsync(userId + i, "Test subject line", "Sample email body");

    //Version 2:
    UserManager.SendEmailAsync(userId + i, "Test subject line", "Sample email body");

    //Version 3:
    ThreadPool.QueueUserWorkItem(o => UserManager.SendEmailAsync(userId + i, "Test subject line", "Sample email body"));

    //Version 4:
    Task.Run(async () => { await UserManager.SendEmailAsync(userId + i, "Test subject line", "Sample email body"); });

    //Version 5:
    await Task.Run(() => { UserManager.SendEmailAsync(userId + i, "Test subject line", "Sample email body"); });
}

版本1是唯一可用的代码。但await将阻止导致长时间延迟的代码,然后再转到下一页。

第2,3和4版本根本不起作用。

版本5有一种非常奇怪的行为。我总是少收一封电子邮件。使用i跟踪我丢失的电子邮件,它始终是最后一个丢失的电子邮件。 (在上面的示例中,我将始终只接收来自userId 1到9的电子邮件。)如果我尝试只发送一封电子邮件,那么我将永远无法收到一封。

出了什么问题?

被修改 感谢大家的回复,但我可能应该提到:

  1. 当我想要解雇时,我几乎可以肯定我拥有我需要的一切,之后的任何过程都不依赖于这个过程。

  2. 我不关心这个过程的成功/失败。 (可能会记录错误,但如果失败则不会太在意)

2 个答案:

答案 0 :(得分:3)

  

...在后台运行该过程,这就是我遇到的问题

您不应该使用Web请求执行长时间运行的请求。首要原因是你必须计划失败。如果应用程序池被回收会发生什么?在这种情况下,工作可能会丢失,你可能会注意到状态是必要的,以便从中断处获取执行(还有在失败后如何重新选择它)?请求也可能在客户端超时,导致用户相信(错误地)存在故障。当你负担不起时,你也可能会嚼掉网站资源(cpu,network io等)。

有各种各样的框架可以帮助您解决这个问题。查看由Scott Hanselman撰写的这篇(IMO)非常好的文章,标题为How to run Background Tasks in ASP.NET。他会遍历各种现有的框架,这些框架可以帮助您实现这一目标,甚至可以根据需要安排将来的工作。

我的建议是始终将这样的流程卸载到Windows服务(或其他服务),而不是在Web请求上执行它们(我的意思是在网站流程/应用程序池 )。

编辑 根据您的上一次修改

我知道这不是你想听到的(对不起),但我认为这不会改变这个答案或其他答案。 ASP.NET(包括MVC,因为它位于asp.net 之上)的目的不是为了执行长时间运行的请求(就像构建和发送5000封电子邮件) 。如果失败,你还提到日志记录,这样的长时间运行请求不能保证也会发生日志记录。应用程序池回收相当于Process.Kill(),即使您有清理代码和日志代码,也无法保证它会执行。你可能永远都不知道是否有失败。

答案 1 :(得分:2)

此问题的一个解决方案是异步触发所有异步电子邮件请求,并将这些任务放入集合中,然后等待任务集合。这可能会快一点,因为每个请求都不必在完成之前等待电子邮件请求,而是它们都可以同时发生。

示例:

var myTasks = new List<Task>();
myTasks.Add(UserManager.SendEmailAsync(userId, "Test subject line", "Sample email body"));
myTasks.Add(UserManager.SendEmailAsync(userId, "Test subject line", "Sample email body"));
myTasks.Add(UserManager.SendEmailAsync(userId, "Test subject line", "Sample email body"));
await Task.WhenAll(myTasks);

如果你想彻底发射并忘记,无论你如何分割它都可能会有风险,请查看Stephen Cleary关于使用QueueBackgroundWorkItem的帖子。我会尝试将所有任务放入一个集合中,等待他们在选择核选项之前判断性能。