The MVC project comes with partially built-in email services. I have implemented the EmailService
class that is working fine. However, I need to send the emails in bulk to multiple users thus I would like to run the process in the background and this is where I'm stuck. I have tried the following codes:
//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"); });
}
Version 1 is the only working code. But the await
will block the code which causes long delay before moving to the next page.
Version 2, 3 and 4 don't work at all.
Version 5 has a very odd behavior. I always get one less email. Using the i
to track which email I'm missing, it's always the last one that is missing. (in above example, I will always receive email from userId 1 to 9 only.) And if I try to send only one email, then I can never receive one.
What went wrong?
Edited Thanks everyone for your response, but I probably should mention that:
At the time I want to "fire and forget', I am almost certain that I have everything I need, and any process after that does not depend on this process.
I do NOT care the success/failure of this process. (Will probably log the error, but would not care much if it fails)
One solution to this problem is to asynchronously fire all of your async email requests, and put those tasks into a collection, then await the collection of tasks. This will probably be a bit faster since each request won't have to wait for the email request before it to complete, instead they can all happen concurrently.
Example:
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);
If you want a full blown fire and forget, which is probably going to be risky no matter how you slice it, check out this post by Stephen Cleary on using QueueBackgroundWorkItem. I would try out putting all of the tasks into a collection though and awaiting them to judge performance before going nuclear option.