2012-03-12 178 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應用程序尤其重要,但可能無法改變這種情況下的線程數。