【问题标题】:Resending email in SendCompleted Event在 SendCompleted 事件中重新发送电子邮件
【发布时间】:2013-05-08 13:37:07
【问题描述】:

我有一个名为 SchedulerService 的类,其中有一个 SendMail 函数,当需要在某个时间发送电子邮件时调用该函数。当我调用 SendMail 函数时,我传入一个对象,该对象包含有关向谁发送电子邮件以及电子邮件来自谁的信息。现在,我添加了一个 SendCompleted 处理程序,以便我可以重新发送电子邮件,以防万一发生导致它无法发送的情况。我有这个发送邮件的代码:

    var recipients = EmailTo.Split(',').ToList();
    if (String.IsNullOrEmpty(EmailFrom))
        EmailFrom = recipients[0];

    using (MailMessage message = new MailMessage())
      {
        message.From = new MailAddress(EmailFrom);
        recipients.ForEach(a => message.To.Add(new MailAddress(a)));
        message.Attachments.Add(new Attachment(LocationOfResults));
        message.Subject = String.Format("{0:MM-dd-yyyy} Results for task: {1}.", DateTime.Now, Description);
        message.Body = "Attached is the results file specified for the task: " + Description;
        smtpClient.SendCompleted += new SendCompletedEventHandler(SendCompletedCallback);
        smtpClient.UseDefaultCredentials = true;
        smtpClient.SendAsync(message, null);

                }

这是事件处理程序

    private void SendCompletedCallback(object sender, AsyncCompletedEventArgs e)
    {
        if (e.Cancelled)
        {
            MailMessage mail = (MailMessage)e.UserState;
            using (mail)
            {
                 smtpClient.Send(mail);
            }
        }
        if (e.Error != null)
        {
            Log(e.Error.ToString() + " in SendCompletedHandlerEvent", EventLogEntryType.Error);
        }

    }

问题是,我发现这样做不起作用,因为 To 和 From 字段为空,这会在发送电子邮件时导致错误。我应该如何从未能发送的电子邮件中回收收件人/发件人字段?

【问题讨论】:

  • 从我看到的地方我会说问题出在您的 UserState 属性中,因为这是您从中加载邮件的地方,但您没有提供足够的代码来查看问题的确切位置。 ..
  • 哦,对不起。我刚刚添加了用于发送电子邮件的代码。我希望这会有所帮助。
  • 你不能从不同的线程同步发送吗?
  • 所以,最初我试图进行修复,因为数据库锁定导致电子邮件无法发送。但是,我不确定这是否是解决问题的合适方法。
  • 不幸的是,您这样做的方式非常错误。只是为了说明 - 如果它第二次失败怎么办?即使您修复了错误,从通知您邮件发送完成的事件发送另一封邮件的完整机制也是错误的。

标签: c#


【解决方案1】:

如果你想使用异步发送,那么你应该去掉using 块,它会处理你初始化的message 变量。相反,您可以从 SendCompleted eventHandler 调用 Dispose() 或仅从初始化方法调用 Dispose:

 /*...*/
 smtpClient.SendAsync(message, null);
 message.Dispose();

您也可以在 this pageMSDN 上找到这种方法

另外,你应该以某种方式重写你的初始化,让它看起来像这样:

smtpClient.Credentials = new NetworkCredential("somemail@gmail.com", "pass");
            smtpClient.Port = 587;
            smtpClient.Host = "smtp.gmail.com";
            smtpClient.SendAsync(message, null);
            smtpClient.SendCompleted  += new SendCompletedEventHandler(smtpClient_SendCompleted);

也就是说,我的意思是SendAsyncCredentails和其他需要执行操作的参数应该先设置。

UPD 它将解决处理原始message 变量的问题,因为这就是“To 和 From 字段为空”的原因。

关于重新发送 - 请提供更多代码或情况示例,无论您将如何以及在何处避免消息发送取消并重新发送。

另外,您在重新发送已取消的消息下到底是什么意思?您想再次发送已取消的消息是否正确?据我了解,您无法继续发送在您的情况下使用 SendAsyncCancel() 取消的消息。

在执行 SendAsyncCancel 的情况下,它仍会引发 SendCompleted 事件,但其传递的参数表明操作已取消。所以,你只是无法逃脱它。你可能想看看this page,当然还有MSDN。

如果你需要发送这么多的消息,以防万一出现取消发送的问题,那就重新发送吧:

  /*your SendCompleted EventHandler*/
 if (e.Canceled)
  {
    //if you use using(message) {...} here, you'll get ObjectDisposedException again
   SmtpClient smtp;
    smtp = new SmtpClient();  
     smtp = GetClient(smtp);  //method of your smtpClient initialization
    MailMessage mess = new MailMessage();
    mess = GetMessage(mess, smtp);  //method of your mailMessage initialization

    try
    {
        //sending message again
        smtp.SendAsync(mess, null);

    }
    catch (ObjectDisposedException e)
    {
       MessageBox.Show("The email message was not sent. See the details:\n"+e.Message,
      "Error sendiing message")

     }
  }

我使用 SendAsyncCancel 方法对其进行了测试,因此我希望考虑到大部分取消原因。

【讨论】:

  • 好的。但是,我不确定这是否有助于解决我试图解决的重新发送电子邮件的原始问题?
  • 请参阅 UPD 部分。如果我说得对,它可能会有所帮助。
【解决方案2】:

您似乎将与同步场景相关的代码与特定于异步场景的代码混合在一起。因此,从异步的角度来看,这是一个基本示例,说明如何安全地首先发送电子邮件:

var recipients = EmailTo.Split(',').ToList();
if (String.IsNullOrEmpty(EmailFrom))
    EmailFrom = recipients[0];

MailMessage message = new MailMessage()
{
    From = new MailAddress(EmailFrom),
    Subject = String.Format("{0:MM-dd-yyyy} Results for task: {1}.", DateTime.Now, Description),
    Body = "Attached is the results file specified for the task: " + Description;
};
recipients.ForEach(a => message.To.Add(new MailAddress(a)));
message.Attachments.Add(new Attachment(LocationOfResults));
smtpClient.SendCompleted += new SendCompletedEventHandler(SendCompletedCallback);
smtpClient.UseDefaultCredentials = true;
smtpClient.SendAsync(message, message); // IMPORTANT - send message as UserState so we can access it in the callback

如何处理回调和处置消息:

MailMessage msg = (MailMessage)e.UserState;
if (e.Cancelled)
{
    // force synchronous send
    smptClient.Send(msg);
}
msg.Dispose(); // dispose of the message as we no longer need it
if (e.Error != null)
{
    Log(e.Error.ToString() + " in SendCompletedHandlerEvent", EventLogEntryType.Error);
}

【讨论】:

    【解决方案3】:

    正如我在评论中所说,你做错了。

    不做错的一种方法是创建一个类似这样的循环:

    • 将“邮件发送成功”标志设置为 false
    • 将“发送”标志设置为 true
    • 尝试异步发送邮件
    • 如果您正在处理,请检查e.Canceled - 如果为假,请将“主要发送成功”标志设置为真。还将“发送”标志设置为 false(您完成了发送过程,对吗?)
    • 一段时间做点别的。如果没有其他可用的,请执行Application.DoEvents()(是的,在很多层面上都是错误的,但现在让我们保持简单)
    • 检查“发送”标志。如果仍然设置,请重复上一步一段时间。使用计数器不会永远卡住。
    • 检查“邮件发送成功”标志。如果未设置,请重复。减少一些计数器,所以你不应该永远这样做。

    为了更好的衡量,把上面所说的一切都放在辅助线程中。而不是 DoEvents() 只是 Sleep(50) 或类似的东西,这样你就不会占用 CPU 时间。

    【讨论】:

      【解决方案4】:

      SendAsync 方法的第二个参数采用可从 e.UserState 读取的用户令牌。因此,将您的 SendAsync 代码更改为:

          smtpClient.SendAsync(message, message);
      

      您的代码将正常工作。

      同样在 SendComplete 事件中,您可以对 sender 进行类型转换以获取实际的 SmtpClient。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-09-22
        • 2017-09-09
        • 1970-01-01
        • 2020-04-24
        • 2014-07-19
        • 1970-01-01
        相关资源
        最近更新 更多