我们目前正在使用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服务关闭或发送电子邮件有延迟时会发生什么情况,线程池线程是否会等到发送电子邮件?
当线程数超过阈值时,标记为//发送电子邮件处理与发送电子邮件无关的部分是否完全执行?
任何解释都非常感谢。
答案 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应用程序尤为重要,但在这种情况下可能不允许更改线程数。