【问题标题】:ThreadPool and sending emails线程池和发送电子邮件
【发布时间】:2012-03-29 08:01:26
【问题描述】:

我们目前正在使用 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。

这是否意味着,只要我们一次发送的电子邮件数量

当线程数超过阈值时,标记为//Post email processesing inrelated to send email的部分是否会被执行?

任何解释都非常感谢。

【问题讨论】:

  • 您的 TCP 堆栈还有其他限制。可以与 SMTP 服务器同时建立 32767 出站 TCP 连接吗?

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


【解决方案1】:

对于 64 位架构,线程池线程的最大数量似乎是 32768。

不是真的,这是 默认 最大线程数。您可以通过调用 ThreadPool.SetMaxThreads() 来更改此设置。

这是否意味着,只要我们一次发送的电子邮件数量

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

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

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

当 SMTP 服务关闭时会发生什么?

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

发送邮件有延迟时会发生什么,线程池线程会一直等到邮件发送完吗?

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

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

【讨论】:

    【解决方案2】:

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

    要考虑的另一件事是 SMTP 服务器是异步的(放入出站文件夹)。此外,正如上面提到的,它可能是一个瓶颈。

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

    【讨论】:

      【解决方案3】:

      在这里使用线程池技术是正确的解决方案。虽然如果可能的话,如果您可以使用 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); 
      } 
      

      【讨论】:

      猜你喜欢
      • 2018-09-22
      • 2015-01-18
      • 1970-01-01
      • 2023-03-06
      • 2012-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-10
      相关资源
      最近更新 更多