1

I'm working on an MVC C# website, .NET 4.0, and in one controller I need to send a lot of mail. I copied my own code from a standalone program where I use SendAsynch passing a callback. In the stanalone application all works.

Of course, in the controller the same code does not work: the callback is never called.

Someone can tell me WHY the same code works standalone and not in a controller?

Here is the main code:

SmtpClient GenMailClient = new SmtpClient();
GenMailClient.SendCompleted += SendCompleted;
GenMailClient.SendAsync(message, ArgumentForTheCallBack);
WaitingMails++;
var startTime = DateTime.Now;
//waits until the SendCompleted is called (FOREVER!)
while (WaitingMails != 0)
    Thread.Sleep(500);    
GenMailClient.Dispose();

And here is the SendCompleted callback:

private void SendCompleted(object sender, AsyncCompletedEventArgs e)
{
    WaitingMails--;
}
Giorgio Forti
  • 131
  • 2
  • 14

1 Answers1

1

That's because SmtpClient.SendAsync captures current SynchronizationContext and executes callback (SendCompleted) on that captured context, if any.

In asp.net mvc (not core) - every request has corresponding synchronization context. You block thread corresponding to that context by

while (WaitingMails != 0)
    Thread.Sleep(500);

This provides no chance for SendCompleted callback to execute, because corresponding thread is blocked, and it's blocked waiting for SendComplete to execute, so you have classic deadlock scenario.

Easiest solution to deal with that is forget about SendAsync and SendCompleted and use async\await capabilities of SmtpClient:

SmtpClient GenMailClient = new SmtpClient();
await GenMailClient.SendMailAsync(message);
// done

Of course for that you will have to rewrite your asp.net mvc actions (at least those which send emails) in asynchronous fasion. If you don't want to do that, another solution is:

SmtpClient GenMailClient = new SmtpClient();
GenMailClient.Send(message);

Because what you are trying to do is emulate synchronous send with asynchronous methods. Why? Just send it synchronously.

Evk
  • 98,527
  • 8
  • 141
  • 191
  • I posted a "simplyfied" version, because I the code performs some things iniside SendCompleted, first of all intercept errors. With "Send" how can i know if there are errors? – Giorgio Forti May 28 '18 at 15:09
  • Can I use "await" with .NET 4.0? And how can I intercept errors without a callback? – Giorgio Forti May 28 '18 at 15:11
  • Maybe removing the "Thread.Sleep(500)" and using a callback, can I dispose thw SMTPClient inside the CallBack? – Giorgio Forti May 28 '18 at 15:13
  • @GiorgioForti errors will be thrown as exception (in both cases), there is no need for callback to get errors. As for .NET 4.0 - you can read this question: https://stackoverflow.com/q/19423251/5311735 – Evk May 28 '18 at 15:13
  • "Errors": the errors I must intercept are of two types: a) wrong destination address b) answers from the provider (policy limit, too many messages ...) Are you sure all these errors will appear as exceptions? – Giorgio Forti May 28 '18 at 15:18
  • Well I guess you can just try it yourself and see. I mean I'm quite sure they will appear as exception, but for you to be sure - just try it. – Evk May 28 '18 at 15:23