简体   繁体   中英

Two ways to send email via SmtpClient asynchronously, different results

Simple concept here. This is for a site being built using MVC 3 and Entity Framework 4. After a user registers on the site, an email is sent to their email address. I first implemented this using SmtpClient.Send() and it worked fine. Then I got the bright idea to try sending the email asynchronously. I'm experiencing issues with the two async approaches I've tried.

First implementation (from this unanswered post: https://stackoverflow.com/questions/7558582/how-to-dispose-using-smtpclient-send-and-asynccallback ):

public bool Emailer(){
    .
    .
    .
    using (var smtpClient = new SmtpClient())
    {
        smtpClient.EnableSsl = true;
        smtpClient.Host = "smtp.gmail.com";
        smtpClient.Port = 587;
        smtpClient.UseDefaultCredentials = false;
        smtpClient.Credentials = new NetworkCredential("myaddress@gmail.com", "mypassword");

        var sd = new SendEmailDelegate(smtpClient.Send);
        var cb = new AsyncCallback(SendEmailResponse);
        sd.BeginInvoke(message, cb, sd);

        return true;
    }
}

private delegate void SendEmailDelegate(System.Net.Mail.MailMessage m);

private static void SendEmailResponse(IAsyncResult ar)
{
    try
    {
        SendEmailDelegate sd = (SendEmailDelegate)(ar.AsyncState);
        sd.EndInvoke(ar); // "cannot access a disposed object" errors here
    }
    catch (Exception e)
    {
        _logger.WarnException("Error on EndInvoke.", e);
    }
}

This worked half the time. The other half I would get a "Cannot access a disposed object" error in the CallBack.

Next implementation (from a member with solid reputation: What are best practices for using SmtpClient, SendAsync and Dispose under .NET 4.0 ):

var smtpClient = new SmtpClient();
smtpClient.EnableSsl = true;
smtpClient.Host = "smtp.gmail.com";
smtpClient.Port = 587;
smtpClient.UseDefaultCredentials = false;
smtpClient.Credentials = new NetworkCredential("myaddress@gmail.com", "mypassword");

smtpClient.SendCompleted += (s, e) =>
    {
        smtpClient.Dispose();
        message.Dispose();
    };
smtpClient.SendAsync(message, null);

With this implementation I dont get any errors, but there's a noticeably longer delay (~5 seconds) in debugging mode when smtpClient.SendAsync() executes, leading me to think that its not being sent asynchronously.

Questions:

1) what's wrong in the first method that's causing the "disposed object" errors?

2) does the second implementation have a problem that's causing the email to not send asynchronously? Is the 5-second delay meaningless?

Might be also important to note that although the site will not need to support sending a large number of emails (only user registration, opt-in newsletters, etc), we do anticipate a large number of users in the future, hence my decision to send emails asynchronously.

Thanks.

Your fist method will not work properly because of the USING-block. After the using-block ends, the SmtpClient object will de disposed. So you can't get access to it in your event handler.

tips: 1-dont use "using block" for MailMessage objet, it dispose your object before mail sent
2-dispose MailMessage objects on SmtpClient.SendCompleted event:

smtpClient.SendCompleted += (s, e) =>
    {
        message.Dispose();
    };

3-set SendCompletedEventHandler for smtpClient object

smtpClient.SendCompleted += new SendCompletedEventHandler(SendCompletedCallback);

4-more code:

private static void SendCompletedCallback(object sender, AsyncCompletedEventArgs e)
    {
        // Get the unique identifier for this asynchronous operation.
        String token = (string)e.UserState;

        if (e.Cancelled)
        {
            //write your code here
        }
        if (e.Error != null)
        {
            //write your code here
        }
        else //mail sent
        {
            //write your code here
        }

        mailSent = true;
    }

SmtpClient.SendAsync is the preferred method of async email sending since it uses SmtpClient methods specially designed for this purpose. It is also simpler to implement and has been proven to work thousands of times.

Your 5 sec delay is strange and suggests there is a problem that needs addressing. First piece of code is just covering the problem up but does not eliminate it.

SmtpClient.SendAsync will actually only send asynchronously if your delivery method is not SpecifiedPickupDirectory or PickupDirectoryFromIis . In those cases it will write the message file into the pickup folder before returning. Check your config file's <smtp> section. My guess is you're using one of these methods and the problem is with the pickup folder. Delete old files you might have there and check if the problem is not your antivirus software which most likely searches each new file for viruses. Check if there is encryption or compression attributes set. Can be something else too. Best way to test if the folder is the source of the problems is by manually coping an email file into it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM