在ASP.NET C#中异步发送电子邮件

3
我想知道是否有一种在asp.net c#中异步发送电子邮件的方法。我已经使用以下代码发送电子邮件:
 if (checkObavijest.Checked)
            {
                List<hsp_Kupci_Newsletter_Result> lista = ServisnaKlasa.KupciNewsletter();
                for (int i = 0; i < lista.Count; i++)
                {
                    MailMessage mail = new MailMessage();

                    mail.From = new MailAddress("*******");
                    mail.To.Add(lista[i].Email);
                    mail.Subject = "Subject";
                    mail.SubjectEncoding = Encoding.UTF8;
                    mail.BodyEncoding = Encoding.UTF8;
                    mail.IsBodyHtml = true;
                    mail.Priority = MailPriority.High;
                    mail.Body = "Some message";
                    SmtpClient smtpClient = new SmtpClient();
                    Object state = mail;
                    smtpClient.Credentials = new NetworkCredential("****@gmail.com", "****");
                    smtpClient.Port = 587;
                    smtpClient.Host = "smtp.gmail.com";
                    smtpClient.EnableSsl = true;
                    smtpClient.SendCompleted += new SendCompletedEventHandler(smtpClient_SendCompleted);
                    try
                    {
                        smtpClient.SendAsync(mail, state);
                    }
                    catch (Exception ex)
                    {

                    }
                }

 void smtpClient_SendCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {

            MailMessage mail = e.UserState as MailMessage;

            if (!e.Cancelled && e.Error != null)
            {
                string poruka = "Emailovi poslani!";
                ClientScript.RegisterStartupScript(this.GetType(), "myalert", "alert('" + poruka + "');", true);
            }
        }

但是当我勾选发送电子邮件选项时,页面会加载约40秒钟,然后在40秒后才会发送电子邮件。您可以看到,我目前从我的数据库中提取了20-30封电子邮件。我以为这是异步发送邮件的正确方法,但显然不是...我该如何将电子邮件发送推送到另一个线程,以便不影响正在使用此页面的用户?有人能帮帮我吗?谢谢!

2个回答

1

这里提到的原因可以在MSDN SmtpClient.SendAsync文档中找到。

调用SendAsync后,在尝试使用Send或SendAsync发送另一封电子邮件消息之前,必须等待电子邮件传输完成。

您可以通过包装方法调用使用线程池来调用SendAsync。 这将为您提供类似于“fire-and-forget”场景的内容。

像这样:

public void SendViaThread(System.Net.Mail.MailMessage message) {
    try {
        System.Threading.ThreadPool.QueueUserWorkItem(SendViaAsync, message);
    } catch (Exception ex) {
        throw;
    }
}

private void SendViaAsync(object stateObject) {
    System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient();
    System.Net.Mail.MmailMessage message = (MmailMessage)stateObject;
    ...
    smtp.Credentials = new NetworkCredential("...");
    smtp.Port = 587;
    smtp.Host = "...";
    ...
    smtp.SendCompleted += new SendCompletedEventHandler(smtpClient_SendCompleted);
    ...
    smtp.Send(message);
}

在你的代码中,你需要调用SendViaThread(mail)。因此,你的循环现在变成了:

for (int i = 0; i < lista.Count; i++) {
    MailMessage mail = new MailMessage();
    mail.From = new MailAddress("...");
    mail.To.Add(lista[i].Email);
    mail.Subject = "...";
    ...
    mail.Body = "...";
    SendViaThread(mail);
 }

我会试一下,但是为什么你将sendviathread函数声明为bool,而不是void呢? - perkes456
哦..那是我一个项目中的一部分代码..我正在做一些更多的工作并返回一个布尔值来指示问题。你其实不需要那个。谢谢指出,我会删除那部分。 - Abhitalks
你能具体说明一下如何将这个集成到我的现有代码中吗?我不太确定我应该在这里做什么... - perkes456
@perkes456:我已经让答案更清晰了。你所需要做的就是在现有的循环中删除与smtpclient相关的代码,然后只需调用SendViaThread(mail)而不是smtpClient.SendAsync(mail, state)即可。 - Abhitalks
只需调用 SendViaThread(mail)。我正在更新答案,以包括您这一侧的代码。 - Abhitalks
显示剩余4条评论

1
如果你不想让主线程等待,可以尝试将其放入一个任务中。
Task.Factory.StartNew( () => {
  // do mail stuff here
});

请注意,每次生成任务时,它都会生成一个新的(或重新使用)系统创建的线程。如果您发送了30封电子邮件,则可能会触发大量线程(系统也有可编程限制)。Task.Factory.StartNew是一种非常简单的方法来执行“启动并忘记”进程。 http://msdn.microsoft.com/en-us/library/dd321439%28v=vs.110%29.aspx(在此示例中,代码还保持了Tasks集合,这很好,如果您想管理它们的话--但实际上,您只需要Task.Factory.StartNew(()=>{部分,如果您想启动并忘记即可。只需小心,因为您不希望有孤立的线程占用您的内存使用情况。

有没有办法在发送邮件完成后处理掉这些线程? - perkes456
不用担心,你可以让系统处理释放这些任务。一旦完成,它们就完成了。-- 参见:http://blogs.msdn.com/b/pfxteam/archive/2012/03/25/10287435.aspx另外,你也可以使用Task.Run(而不是.StartNew)-- 也要研究一下。如果你想追踪你的任务,你可以像示例中所列出的那样将它们添加到一个集合中,或者在启动时包装它们(using会自我清理和本地化)。但我建议只让.NET处理清理,不要担心它。 - Jason
过去,我曾将生成的线程添加到集合中,以便能够监视它们的进程、了解它们正在做什么,并在需要时将其删除(多线程是一个有趣的[不]领域)。Task.Run/StartNew使得您可以轻松入门,而无需过多担心线程状态、竞争条件等。非常适合您的“点火即忘”场景。 - Jason
我在使用我在问题中发布的第一段代码时出现了“内存不足”的错误,这可能是错误的原因吗? - perkes456

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