使用SmtpClient异步发送电子邮件的两种方法,结果不同。

8
简单的概念在这里。这是一个使用MVC 3和Entity Framework 4构建的网站。用户注册后,会向他们的电子邮件地址发送一封电子邮件。我最初使用SmtpClient.Send()来实现这个功能,并且它工作得很好。然后我想到了异步发送电子邮件的好主意。我尝试了两种异步方法,但遇到了问题。
第一个实现方法(来自这篇未回答的帖子: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);
    }
}

这个方法有时候有效,但是另一半时间我会在回调函数中遇到"Cannot access a disposed object"的错误。

下一个实现(来自一个信誉良好的成员: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);

使用这种实现方法没有出现任何错误,但是在调试模式下smtpClient.SendAsync()执行时有明显的延迟(约5秒),这使我认为它没有异步发送。
问题:
1)第一种方法中有什么问题会导致“disposed object”错误?
2)第二种实现方式是否存在导致电子邮件无法异步发送的问题?5秒的延迟是否无意义?
还值得注意的是,虽然该站点不需要支持发送大量电子邮件(仅用户注册、选择接收新闻通讯等),但我们预计未来会有大量用户,因此我决定异步发送电子邮件。
谢谢。
3个回答

7

你的第一个方法无法正常工作,因为使用了 USING 块。当 USING 块结束时,SmtpClient 对象将被处理。因此,在事件处理程序中无法访问它。


谢谢回复。我现在明白了,在处理程序有机会触发之前,对象已被释放。此外,我理解必须同时处理SmtpClient和调用EndInvoke。第二种方法是否实现了这一点? - John L

5

提示: 1-不要在MailMessage对象中使用"using块",因为它会在邮件发送之前处理你的对象
2-在SmtpClient.SendCompleted事件中处理MailMessage对象:

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

为smtpClient对象设置3-set SendCompletedEventHandler

smtpClient.SendCompleted += new SendCompletedEventHandler(SendCompletedCallback);

更多代码:

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;
    }

1

SmtpClient.SendAsync 是异步发送电子邮件的首选方法,因为它使用了专门为此目的设计的 SmtpClient 方法。它的实现也更简单,并且已经被证明可以正常工作数千次。

你的5秒延迟很奇怪,这表明存在需要解决的问题。第一段代码只是掩盖了问题,但并没有消除它。

SmtpClient.SendAsync 只有在你的传递方式不是 SpecifiedPickupDirectoryPickupDirectoryFromIis 时才会异步发送。在这些情况下,它会在返回之前将消息文件写入接收文件夹中。检查你的配置文件的 <smtp> 部分。我猜你正在使用其中一种方法,并且问题可能出在接收文件夹上。删除可能存在的旧文件,并检查问题是否与你的防病毒软件有关,它很可能会对每个新文件进行病毒搜索。检查是否设置了加密或压缩属性。也可能是其他原因。测试文件夹是否是问题源的最佳方法是手动将电子邮件文件复制到其中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接