ThreadPool和发送电子邮件

时间:2012-03-12 21:42:47

标签: .net multithreading .net-4.0 asp.net-4.0 threadpool

我们目前正在使用ThreadPool向用户发送异步电子邮件。从本质上讲,我们的逻辑可归结为:

for (int i=0 < i < numUsers; i++) 
{

   //Pre email processing unrelated to sending email

   string subject = GetSubject();
   string message = GetMessage();
   string emailAddress = GetEmailAddress();

   EmailObj emailObj = new EmailObj { subject = subject, message = message, emailAddress = emailAddress };

   bool sent = ThreadPool.QueueUserWorkItem(new WaitCallback(SendEmail), emailObj);

   //Post email processing unrelated to sending email
}

public void SendEmail(object emailObj)
{
    //Create SMTP client object
    SmtpClient client = new SmtpClient(Configuration.ConfigManager.SmtpServer);

    //Logic to set the subject, message etc
    client.Send(mail);
}

到目前为止,逻辑效果很好,用户数量很少。我们正在尝试扩展此功能,以便能够发送大约一百万封电子邮件。

根据MSDN,线程池线程的最大数量基于内存,根据此SO answer,对于64位体系结构,似乎最大线程池线程数为32768.

这是否意味着,只要我们一次发送的电子邮件数量为&lt; 32768,我们应该没事?超过这个数字会发生什么?当SMTP服务关闭或发送电子邮件有延迟时会发生什么情况,线程池线程是否会等到发送电子邮件?

当线程数超过阈值时,标记为//发送电子邮件处理与发送电子邮件无关的部分是否完全执行?

任何解释都非常感谢。

3 个答案:

答案 0 :(得分:3)

线程有开销 - 1MB线程本地存储。你永远不希望你的线程池中有32K线程。线程池用于门控和共享线程,因为它们有开销。如果线程池达到饱和,则将来的调用排队并等待池中的可用线程。

另一件需要考虑的事情是SMTP服务器是异步的(放入出站文件夹)。另外,如上所述,它可以是瓶颈。

一种选择是通过增加“代理”发送邮件的数量来增加吞吐量,并增加SMTP服务器的数量以扩展解决方案。能够独立扩展代理和SMTP服务器可以解决瓶颈问题。

答案 1 :(得分:2)

使用线程池技术是正确的解决方案。虽然如果可能的话我会使用Task类,但是ThreadPool.QueueUserWorkItem也是一个很好的路线。我怀疑ThreadPool实际上会创建32768个线程,即使它可能是理论上的最大值。线程不是一个廉价的资源,所以它将实际数量保持在最低限度。但是,有多少实际线程并不重要。所有工作项都排队等候。即使只有一个线程处理队列,它最终也会被清空。

百万是很多电子邮件。根据保存电子邮件数据的数据结构的大小,如果您尝试一次排队,则可能会出现内存问题。您可以实现某种限制策略,以保持活动对象的数量较少,从而使内存压力保持在正常范围内。信号量将是一个有用的工具,可以帮助你扼杀事物。

var semaphore = new SemaphoreSlim(10000, 10000); // Allow 10000 work items at a time
for (int i=0 < i < numUsers; i++)  
{ 
   semaphore.Wait(); // Blocks if there are too many pending work items
   string subject = GetSubject(); 
   string message = GetMessage(); 
   string emailAddress = GetEmailAddress(); 
   EmailObj emailObj = new EmailObj { subject = subject, message = message, emailAddress = emailAddress };  
   bool sent = ThreadPool.QueueUserWorkItem(
      (state) =>
      {
        try
        {
          SendEmail(emailObj);
        }
        finally
        {
          semaphore.Release(); // This work item is done.
        }
      }, null); 
} 

答案 2 :(得分:2)

  

对于64位体系结构,似乎最大线程池线程数为32768。

不是,这是默认最大线程数。您可以致电ThreadPool.SetMaxThreads()来更改此内容。

  

这是否意味着,只要我们一次发送的电子邮件数量为&lt; 32768,我们应该没事?超过这个数字会发生什么?

即使电子邮件数量超过该阈值,您也会没事的。 ThreadPool的重点是汇集线程。这意味着只有当它认为您的性能将从中受益时,它才会创建更多线程。即使您尝试一次发送数万封电子邮件,也不太可能创建那么多线程。

ThreadPool认为您不会从创建另一个线程中受益并且您向其添加更多工作时,它将排队并在其他线程完成时或在ThreadPool更改时进行处理它的思想和创造新的线索。

因此,创建许多工作项是安全的,但它可能会导致另一个问题:饥饿。如果您创建的电子邮件速度超过了发送速度,那么您就会遇到很大的问题。

  

关闭SMTP服务后会发生什么?

Send()会抛出异常。而且由于它似乎没有抓住它,它会使整个应用程序崩溃。如果由于其他原因无法发送电子邮件,也会发生同样的情况。

  

当发送电子邮件有延迟时会发生什么情况,线程池线程是否会等到发送电子邮件?

是的,当电子邮件发送到服务器时,线程会阻塞。如果某个其他电子邮件等待发送,ThreadPool可以检测到,并且可能会创建另一个线程。但这可能不是你想要的,它可能会使服务器更慢。

为了解决这个问题,您可能希望限制ThreadPool个线程的最大数量。但这是您的应用程序的全局设置。如果您将Task与自定义TaskSheduler一起使用可能会更好,这可以让您限制同时发送的电子邮件数量,但不会限制可能发生的其他工作ThreadPool。这对于使用ThreadPool处理请求的ASP.NET应用程序尤为重要,但在这种情况下可能不允许更改线程数。