-2

My system (.Net 4.8) sends e-mail, and today we have await before each call. It works fine but takes some seconds to send each e-mail, meanwhile user just keeps waiting.

Trying to speed up this proccess, we tried to apply _ = to that line, but then eventually we got this error:

An asynchronous module or handler completed while an asynchronous operation was still pending

I've also tried this other approach Task.Run but then SendEmailMessage wouldn't even run.

After some research I've understood that ASP.NET doesn't work well under this circustances (reference: here and here). Since all request have being fulfilled there is no need to keep that process ongoing; so when the main process CreateNewProduct finishes, SendEmailMessage still is in progress but is also terminated.

I was wondering if there is a way to make .Net work with this situation. Maybe to create a new stack or something to process these async process? I did see some suggestions like to use RabbitMQ but I wouldn't like to have additional services running.

public async Task<JsonResult> CreateNewProduct(string productID){

    //Code here

    //current approach (several e-mails)
    await SendEmailMessage(messageBody1);
    await SendEmailMessage(messageBody2); 
    await SendEmailMessage(messageBody3); 

    //new approach 1
    _ = SendEmailMessage(messageBody1);

    //new approach 2
    Task.Run(() => SendEmailMessage(messageBody1));

    return Json(new
    {
        success = true,
    }, JsonRequestBehavior.AllowGet);
}

public async Task SendEmailMessage(string messageBody){

    using (var client = new MailKit.Net.Smtp.SmtpClient())
    {
        client.ServerCertificateValidationCallback = (s, c, h, e) =>
        {
            Console.WriteLine(e);
            return true;
        };
        await client.ConnectAsync(_SMTPserver, _SMTPport, true);
        await client.AuthenticateAsync(_address, _password);
        await client.SendAsync(messageBody);
        client.Disconnect(true);
    }
}
rd1218
  • 134
  • 12
  • Task.Run() is the obvious approach. But then don't make SendEmailMessage() async. – Hans Passant Jun 07 '23 at 21:24
  • @HansPassant I've created ```public void SendEmailMessageSYNC```, applied ```Task.Run()``` but got the same error "An asynchronous module or handler completed while an asynchronous operation was still pending" – rd1218 Jun 07 '23 at 21:57
  • The best you can do is to have a local smtp-server (f.i. postfix on linux) that will receive the mails and is responsible for the real delivery. – Sir Rufo Jun 07 '23 at 23:32
  • While not being robust against webserver shutdowns etc. you can consider background workers like shown here: https://blog.elmah.io/async-processing-of-long-running-tasks-in-asp-net-core/. The alternative is probably publishing a message on a queue (like you mention) and sending the emails in a consumer service. – ThomasArdal Jun 08 '23 at 08:05
  • @HansPassant Maybe I did something wrong at your suggestion. If possible, please comment at the code I've done, its similar to your message – rd1218 Jun 09 '23 at 12:52

2 Answers2

1

If you want to know if email was actually sent, you have to wait. There is no way around it. However, you can send several emails at the same time (assuming concurrent calls to SendEmailMessage share no resources):

var task1 = SendEmailMessage(messageBody1);
var task2 = SendEmailMessage(messageBody2); 
var task3 = SendEmailMessage(messageBody3);

await Task.WhenAll (task1, task2, task3);

However, if you'd want to send hundreds or thousands, you'd have to organize some kind of queue with background processing.

n0rd
  • 11,850
  • 5
  • 35
  • 56
  • In this case I actually don't want to know if email was sent. There are other places where I can manually resend an e-mail (with the wait time) and where we can check if e-mail was sent (database log). – rd1218 Jun 07 '23 at 21:35
  • @rd1218: https://stackoverflow.com/questions/50756399/safe-way-to-implement-a-fire-and-forget-method-on-asp-net-core relevant? – n0rd Jun 07 '23 at 21:36
0

For now, what I managed to do is an updated code and start studying more about Hangfire and Quartz.NET for future improvements.

public async Task<JsonResult> CreateNewProduct(string productID){

    //Code here

    try
    {
        _ = Task.Run(() => SendEmailMessage(messageBody));
    }
    catch (Exception ex)
    {
        //Deal with exception
    }

    return Json(new
    {
        success = true,
    }, JsonRequestBehavior.AllowGet);
}
rd1218
  • 134
  • 12