后台进程中的 SendEmailAsync
SendEmailAsync in background process
MVC 项目带有部分内置的电子邮件服务。我已经实施了工作正常的 EmailService
class。但是,我需要将电子邮件批量发送给多个用户,因此我想 运行 在后台处理这个过程,这就是我遇到的问题。我试过以下代码:
//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
跟踪我丢失的电子邮件,它总是丢失的最后一封。 (在上面的示例中,我将始终只收到来自用户 ID 1 到 9 的电子邮件。)如果我尝试只发送一封电子邮件,那么我永远收不到一封。
出了什么问题?
已编辑
感谢大家的回复,但我可能应该提一下:
当时我想“一劳永逸”,我几乎可以肯定我拥有我需要的一切,之后的任何过程都不依赖于这个过程。
我不关心这个过程的success/failure。 (可能会记录错误,但如果失败则不会太在意)
此问题的一个解决方案是异步触发所有异步电子邮件请求,并将这些任务放入一个集合中,然后等待任务集合。这可能会快一点,因为每个请求在完成之前不必等待电子邮件请求,而是它们可以同时发生。
示例:
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 的 post 关于使用 QueueBackgroundWorkItem 的内容。不过,我会尝试将所有任务放入一个集合中,并等待他们在进行核选项之前判断性能。
...run the process in the background and this is where I'm stuck
您不应使用 Web 请求执行很长的 运行 请求。第一个原因是你必须为失败做好计划。如果应用程序池被回收会怎样?在那种情况下,工作可能会丢失,您可能会注意到有必要的状态来从它停止的地方继续执行(还有在失败后您将如何恢复它)?请求也可能在客户端超时,导致用户(错误地)认为出现了故障。当您负担不起时,您可能还会消耗网站资源(cpu、网络 io 等)。
有多种框架可以帮助您解决这个问题。查看 Scott Hanselman 撰写的这篇 (IMO) 非常好的文章,标题为 How to run Background Tasks in ASP.NET。他遍历了各种现有框架,这些框架允许您这样做,如果您愿意,甚至可以安排将来要完成的工作。
虽然我的建议是始终将这样的进程卸载到 windows 服务(或其他服务),而不是在网络请求上执行它们(我的意思是在网站上process/application池).
编辑 基于您上次的编辑
我知道这不是您想听到的(抱歉),但我认为这不会改变此答案或其他给出的答案。 ASP.NET(包括 MVC,因为它位于 asp.net 之上)并不是为了执行长 运行 请求而设计的(喜欢创建和发送 5000 封电子邮件)。如果失败,您还提到了日志记录,像这样的长 运行 请求也不能保证日志记录也会发生。应用程序池回收相当于 Process.Kill(),即使您有清理代码和日志记录代码,也不能保证它会执行。你可能永远不知道有没有失败。
MVC 项目带有部分内置的电子邮件服务。我已经实施了工作正常的 EmailService
class。但是,我需要将电子邮件批量发送给多个用户,因此我想 运行 在后台处理这个过程,这就是我遇到的问题。我试过以下代码:
//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
跟踪我丢失的电子邮件,它总是丢失的最后一封。 (在上面的示例中,我将始终只收到来自用户 ID 1 到 9 的电子邮件。)如果我尝试只发送一封电子邮件,那么我永远收不到一封。
出了什么问题?
已编辑 感谢大家的回复,但我可能应该提一下:
当时我想“一劳永逸”,我几乎可以肯定我拥有我需要的一切,之后的任何过程都不依赖于这个过程。
我不关心这个过程的success/failure。 (可能会记录错误,但如果失败则不会太在意)
此问题的一个解决方案是异步触发所有异步电子邮件请求,并将这些任务放入一个集合中,然后等待任务集合。这可能会快一点,因为每个请求在完成之前不必等待电子邮件请求,而是它们可以同时发生。
示例:
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 的 post 关于使用 QueueBackgroundWorkItem 的内容。不过,我会尝试将所有任务放入一个集合中,并等待他们在进行核选项之前判断性能。
...run the process in the background and this is where I'm stuck
您不应使用 Web 请求执行很长的 运行 请求。第一个原因是你必须为失败做好计划。如果应用程序池被回收会怎样?在那种情况下,工作可能会丢失,您可能会注意到有必要的状态来从它停止的地方继续执行(还有在失败后您将如何恢复它)?请求也可能在客户端超时,导致用户(错误地)认为出现了故障。当您负担不起时,您可能还会消耗网站资源(cpu、网络 io 等)。
有多种框架可以帮助您解决这个问题。查看 Scott Hanselman 撰写的这篇 (IMO) 非常好的文章,标题为 How to run Background Tasks in ASP.NET。他遍历了各种现有框架,这些框架允许您这样做,如果您愿意,甚至可以安排将来要完成的工作。
虽然我的建议是始终将这样的进程卸载到 windows 服务(或其他服务),而不是在网络请求上执行它们(我的意思是在网站上process/application池).
编辑 基于您上次的编辑
我知道这不是您想听到的(抱歉),但我认为这不会改变此答案或其他给出的答案。 ASP.NET(包括 MVC,因为它位于 asp.net 之上)并不是为了执行长 运行 请求而设计的(喜欢创建和发送 5000 封电子邮件)。如果失败,您还提到了日志记录,像这样的长 运行 请求也不能保证日志记录也会发生。应用程序池回收相当于 Process.Kill(),即使您有清理代码和日志记录代码,也不能保证它会执行。你可能永远不知道有没有失败。