2012-03-12 180 views
2

我们正在使用ThreadPool发送电子邮件给用户asynchronoulsy。从本质上讲,我们有这样的逻辑:线程池和发送电子邮件

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); 
} 

该逻辑到目前为止效果很好,用户数量很少。我们正在努力扩展这个功能,以便能够发送100多万封电子邮件。

每MSDN,线程池的最大线程数是基于存储器,并根据此SO answer,对于64位架构,似乎线程池中的线程的最大数量为32768

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

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

任何解释都非常感谢。

+2

使用任务,而不是ThreadPool。 – 2012-03-12 21:45:33

+2

你的TCP堆栈还有其他限制。你可以做到SMTP服务器的32767同时出站TCP连接吗? – user957902 2012-03-12 21:53:08

回答

3

线程有开销 - 1MB线程本地存储。你永远不会想要在你的线程池中有32K线程。一个线程池用于关闭和共享线程,因为它们有开销。如果线程池饱和,将来的调用将排队并等待池中的可用线程。

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

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

2

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

100万是相当多的电子邮件。根据您的电子邮件数据的数据结构有多大,如果您尝试将它们全部排列在一起,您可能会遇到内存问题。您可以实施某种节流策略,以将活动对象的数量保持在较低水平,从而将内存压力保持在正常范围内。信号量可以帮助你节流。

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); 
} 
+0

请在此处查看后续问题 - http://stackoverflow.com/questions/9691848/threadpool-and-btstackserver-exception-net – Nick 2012-03-13 21:05:48

2

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

不是真的,这就是线程默认最大数量。您可以致电ThreadPool.SetMaxThreads()更改此项。

这是否意味着,只要我们在同一时间发送邮件的数量< 32768,我们应该罚款?超过这个数字会发生什么?

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

ThreadPool认为你不会从创建另一个线程中获益,并添加更多的工作,它会得到排队,将被处理时,其他线程结束,或者当ThreadPool改变主意,并创建新的线程。

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

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

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

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

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

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