【问题标题】:BackgroundWorker stopping when a handled exception happens当发生处理的异常时,BackgroundWorker 停止
【发布时间】:2014-01-02 16:22:39
【问题描述】:

在一个 WinForms 应用程序中,我运行多个 BackgroundWorker 来发送批量电子邮件。 (不,这不是垃圾邮件,而是公司电子邮件)。

发送电子邮件时,有时会发生SmtpException('发送邮件失败')。此 SmtpException 在 try-catch 中抛出并写入日志。但是,当这个异常特别发生时,将异常写入日志并继续大约需要 5 分钟,之后每封电子邮件都会失败并出现相同的异常(处理每封失败的电子邮件大约需要 5 分钟)。

根据我的研究,我了解到当在调试模式下引发未处理的异常时,BackgroundWorker 会停止。但是,我已经使用调试和发布配置运行了该项目,并且行为是相同的。 即使在调试模式下,BackgroundWorker 也会停止很长时间,而调试器不会弹出任何异常消息。

另请注意,此错误很难重现,因为第一个带有消息“发送邮件失败”的 SmtpException 在看似随机的点发送了 300-800 封电子邮件时引发。

对于日志记录,我只是使用 System.Diagnostics 中的 Trace,所以我认为这不是问题。

编辑:我还应该补充一点,在极少数情况下,当 SmtpException 出现消息“发送邮件失败”时,BackgroundWorker 会完全停止处理电子邮件。我让它挂在那里只是为了它的地狱,大约 1 小时后它继续发送电子邮件,就像什么都没发生一样。

DoWork 的代码如下:

private void sender_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;
    SalaryEmail[] emails = e.Argument as SalaryEmail[];

    SmtpClient smtpClient = new SmtpClient("smtp.office365.com", 587);
    smtpClient.EnableSsl = true;
    smtpClient.Credentials = new NetworkCredential(SalaryEmail.SmtpCredentials.User, SalaryEmail.SmtpCredentials.Password);

    foreach (SalaryEmail email in emails)
    {
        if (worker.CancellationPending == true)
        {
            e.Cancel = true;
            break;
        }
        else
        {
            // Sending and database operations here
            email.Send(ref smtpClient);

            // In this case I only report progress to a progressBar with previously set steps, so the number is irrelevant
            worker.ReportProgress(0);
        }
    }

    smtpClient.Dispose();
}

这是 SalaryEmail.Send:

public bool Send(ref SmtpClient smtpClient)
{
    bool sent = false;

    for (int i = 0; i < MAX_ATTEMPTS; i++)
    {
        try
        {
            // Mail is a System.Net.Mail.MailMessage non-static property created and configured previously within the class
            smtpClient.Send(Mail); 
            sent = true;
        }
        catch (SmtpException smtpEx)
        {
            Trace.WriteLine(DateTime.Now + " Intento " + i + " SmtpException: " + smtpEx.Message);
        }
        catch (Exception ex)
        {
            Trace.WriteLine(DateTime.Now + " Intento " + i + " Error al enviar: " + ex.Message);
        }

        // Update database
        if (sent)
        {
            try
            {
                Database.MarkAsSent(Qna, RFC);
            }
            catch (Exception e)
            {
                Trace.WriteLine(DateTime.Now + " Error al marcar enviados" + e.Message);
            }

            break;
        }
    }

    return sent;
}

异常详情:

A first chance exception of type 'System.Net.Mail.SmtpException' occurred in System.dll
02/01/2014 12:57:25 Intento 0 SmtpException: System.Net.Mail.SmtpException: Error al enviar correo. ---> System.Net.WebException: Se excedió el tiempo de espera de la operación.
   en System.Net.ConnectionPool.Get(Object owningObject, Int32 result, Boolean& continueLoop, WaitHandle[]& waitHandles)
   en System.Net.ConnectionPool.GetConnection(Object owningObject, GeneralAsyncDelegate asyncCallback, Int32 creationTimeout)
   en System.Net.Mail.SmtpConnection.GetConnection(ServicePoint servicePoint)
   en System.Net.Mail.SmtpTransport.GetConnection(ServicePoint servicePoint)
   en System.Net.Mail.SmtpClient.GetConnection()
   en System.Net.Mail.SmtpClient.Send(MailMessage message)
   --- Fin del seguimiento de la pila de la excepción interna ---
   en System.Net.Mail.SmtpClient.Send(MailMessage message)
   en MailingClient.SalaryEmail.Send(SmtpClient& smtpClient) en C:\Users\mrivera\Documents\Visual Studio 2010\Projects\PayrollMailing\MailingClient\SalaryEmail.cs:línea 62

追踪:

02/01/2014 1:44:56 Sending email...
02/01/2014 1:45:01 Updating database...
02/01/2014 1:45:01 Sending email...
02/01/2014 1:45:06 Updating database...
02/01/2014 1:45:07 Sending email...
02/01/2014 1:45:11 Updating database...
02/01/2014 1:45:11 Sending email...
<exception posted above here>
The thread '<No Name>' (0x1890) has exited with code 0 (0x0).
The thread '<No Name>' (0x444) has exited with code 0 (0x0).
The thread '<No Name>' (0xb90) has exited with code 0 (0x0).
The thread '<No Name>' (0x1b00) has exited with code 0 (0x0).
The thread '<No Name>' (0x2754) has exited with code 0 (0x0).
The thread '<No Name>' (0x978) has exited with code 0 (0x0).
The thread '<No Name>' (0x1a68) has exited with code 0 (0x0).
The thread '<No Name>' (0x18b0) has exited with code 0 (0x0).
The thread '<No Name>' (0x1b68) has exited with code 0 (0x0).
The thread '<No Name>' (0x24fc) has exited with code 0 (0x0).
The thread '<No Name>' (0x18c0) has exited with code 0 (0x0).
The thread '<No Name>' (0xa1c) has exited with code 0 (0x0).

【问题讨论】:

  • 看一下 InnerException 看看它是否给你一个更好的线索。我的猜测是服务器无法跟上大量传入请求,5 分钟对我来说听起来像是超时。
  • @WonkotheSane Smtp 超时异常也会发生,但它们得到了正确处理。消息“提交率已超出”也有一个例外,该消息也得到了正确处理。我将再次重现它并查看 InnerException,并将结果发回此处。
  • @WonkotheSane 我的 Visual Studio 是西班牙语,所以我会尽力翻译内部异常消息:“无法将数据写入传输连接。现有连接已被远程主机中断”
  • '提交率已超过',然后服务器应用 5 分钟超时限制。谜团解开了。
  • 贴出的代码无关紧要,显示(主要部分)email.Send()

标签: c# email smtp backgroundworker


【解决方案1】:

BackgroundWorker 因异常而停止,该异常可以在 worker 的 Completed 事件中找到。您可以检查它的堆栈跟踪。为了停止由于异常引起的进程抢占,你应该在 try catch 中有 DoWork

如果操作引发了您的代码无法处理的异常, BackgroundWorker 捕获异常并将其传递给 RunWorkerCompleted 事件处理程序,其中它作为错误公开 System.ComponentModel.RunWorkerCompletedEventArgs 的属性。如果你 在 Visual Studio 调试器下运行,调试器会中断 在 DoWork 事件处理程序中未处理的异常处 被提出来了。

注册 RunWorkerCompleted 事件以获取详细的异常跟踪

public void MyProg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
    if (e.Error != null) {
        // You have an exception, which you can examine through the e.Error property.
        //display your exception details here
    } else {
        // No exception in DoWork.
       //happy ending
        }
    }
}

关于发送批量电子邮件,我建议使用 SendAsync 而不是 SmtpClient 的 Send 方法。同样由于某种原因,您的 smtp 可能会拒绝从您的应用程序接收电子邮件,因此请尝试封装您的每封电子邮件发送逻辑并尝试调试您发现异常的特定电子邮件,并尝试使用某些阈值(每秒 30 封邮件)降低邮件/秒) 以避免 SMTP 服务器过度活跃。

【讨论】:

  • BackgroundWorker 中抛出的所有异常都在 try-catch 中,因此它们由用户代码处理。据我所知,没有未处理的异常,因为调试器在调试模式下运行时不会中断。
  • 你有 DoWork 方法一个主要的 try catch 吗?
  • 不,我没有在 try-catch 中包含全部内容。只是电子邮件构造、发送和数据库操作都在 try-catch 中。我从阅读文档中了解到,如果有任何异常我的代码未处理,调试器将在我运行它时中断(显然是在调试模式下)。但是,在这种情况下,调试器不会中断。
  • 我已添加 DoWork 代码,如果您希望我添加更多代码,请告诉我。
  • 你的代码看起来不错,如果你想发送多封电子邮件,我建议使用 SendAsync 而不是 SmtpClient 的 Send 方法。同样由于某种原因,您的 smtp 可能拒绝从您的应用接收电子邮件,因此请尝试封装您的每封电子邮件发送逻辑并尝试调试您发现异常的特定电子邮件。
猜你喜欢
  • 2012-05-05
  • 1970-01-01
  • 1970-01-01
  • 2018-07-30
  • 1970-01-01
  • 1970-01-01
  • 2010-11-05
  • 1970-01-01
相关资源
最近更新 更多