We have some (synchronous) email code, which creates a class that creates an SmtpClient, and then sends an email. The SmtpClient is not reused; however we get the following exception every now and then:
System.Web.HttpUnhandledException (0x80004005): Exception of type 'System.Web.HttpUnhandledException' was thrown. ---> System.InvalidOperationException: An asynchronous call is already in progress. It must be completed or canceled before you can call this method.
at System.Net.Mail.SmtpClient.Send(MailMessage message)
at EmailSender.SendMail(MailAddress fromMailAddress, string to, String subject, String body, Boolean highPriority) in ...\EmailSender.cs:line 143
Code looks like this:
// ...
var emailSender = new EmailSender();
emailSender.SendMail(toEmail, subject, body, true);
// emailSender not used past this point
// ...
public class EmailSender : IEmailSender
{
private readonly SmtpClient smtp;
public EmailSender()
{
smtp = new SmtpClient();
}
public void SendMail(MailAddress fromMailAddress, string to, string subject, string body, bool highPriority)
{
if (fromMailAddress == null)
throw new Exception();
if (to == null)
throw new ArgumentException("No valid recipients were supplied.", "to");
// Mail initialization
var mailMsg = new MailMessage
{
From = fromMailAddress,
Subject = subject,
Body = body,
IsBodyHtml = true,
Priority = (highPriority) ? MailPriority.High : MailPriority.Normal
};
mailMsg.To.Add(to);
smtp.Send(mailMsg);
}
}
You need to dispose of the SmtpClient using Dispose
, using
or by implementing the disposable pattern for your class EmailSender (which more appropriate here because you are tying the lifetime of the SmtpClient to the lifetime of the EmailSender in the constructor.)
That might solve this exception.
My guess is that the SmtpClient
was not designed to send multiple messages concurrently.
I would change the class like this instead:
public class EmailSender : IEmailSender
{
Queue<MailMessage> _messages = new Queue<MailMessage>();
SmtpClient _client = new SmtpClient();
public EmailSender()
{
}
public void SendMail(MailAddress fromMailAddress, string to, string subject, string body, bool highPriority)
{
if (fromMailAddress == null)
throw new ArgumentNullException("fromMailAddress");
if (to == null)
throw new ArgumentException("No valid recipients were supplied.", "to");
// Mail initialization
var mailMsg = new MailMessage
{
From = fromMailAddress,
Subject = subject,
Body = body,
IsBodyHtml = true,
Priority = (highPriority) ? MailPriority.High : MailPriority.Normal
};
mailMsg.To.Add(to);
lock (_messages)
{
_messages.Enqueue(mailMsg);
if (_messages.Count == 1)
{
ThreadPool.QueueUserWorkItem(SendEmailInternal);
}
}
}
protected virtual void SendEmailInternal(object state)
{
while (true)
{
MailMessage msg;
lock (_messages)
{
if (_messages.Count == 0)
return;
msg = _messages.Dequeue();
}
_client.Send(msg)
}
}
}
As there are really no reason to create the client in the constructor.
I also changed so that the class throws ArgumentNullException
and not Exception
if fromMailAddress
is null. An empty Exception
doesn't say much..
Update
The code do now use a thread pool thread for the sending (and reusing the smtpclient).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With