眾所周知,使用執行緒可以極大的提高應用程式的效率和響應性,提高使用者體驗,但是不可以無節制的使用執行緒,為什麼呢?
執行緒的開銷實際上是非常大的,我們從空間開銷和時間開銷上分別討論。
執行緒的空間開銷來自這四個部分:
OutOfMemoryException
。執行緒的時間開銷來自這三個過程:
執行緒建立的時候,系統相繼初始化以上這些記憶體空間。
接著CLR會呼叫所有載入DLL的DLLMain方法,並傳遞連線標誌(執行緒終止的時候,也會呼叫DLL的DLLMain方法,並傳遞分離標誌)。
執行緒上下文切換。一個系統中會載入很多的程序,而一個程序又包含若干個執行緒。但是一個CPU核心在任何時候都只能有一個執行緒在執行。為了讓每個執行緒看上去都在執行,系統會不斷地切換「執行緒上下文」:每個執行緒及其短暫的執行時間片,然後就會切換到下一個執行緒了。
這個執行緒上下文切換過程大概又分為以下5個步驟:
Spinlock
,並確定下一個要執行的執行緒,然後釋放 Spinlock
。如果下一個執行緒不在同一個程序內,則需要進行虛擬地址交換。所以,由於要進行如此多的工作,所以建立和銷燬一個執行緒就意味著代價「昂貴」,即使現在的CPU多核多執行緒,如無節制的使用執行緒,依舊會嚴重影響效能。
為了免程式設計師無節制地使用執行緒,微軟開發了「執行緒池」技術。簡單來說,執行緒池就是替開發人員管理工作執行緒。當一項工作完畢時,CLR不會銷燬這個執行緒,而是會保留這個執行緒一段時間,看是否有別的工作需要這個執行緒。至於何時銷燬或新起執行緒,由CLR根據自身的演演算法來做這個決定。
執行緒池技術能讓我們重點關注業務的實現,而不是執行緒的效能測試。
微軟除實現了執行緒池外,還需要關注一個型別:BackgroundWorker
。 BackgroundWorker
是在內部使用了執行緒池的技術:同時,在WinForm或WPF編碼中,它還給工作執行緒和UI執行緒提供了互動的能力。
實際上, Thread
和 ThreadPool
預設都沒有提供這種互動能力,而 BackgroundWorker
卻通過事件提供了這種能力。這種能力包括:報告進度、支援完成回撥、取消任務、暫停任務等。
BackgroundWorker
的簡單範例如下:
private BackgroundWorker backgroundWorker = new BackgroundWorker();
private void AsyncButton_Click(object sender, RoutedEventArgs e)
{
//註冊要執行的任務
backgroundWorker.DoWork += BackgroundWorker_DoWork;
//註冊報告進度
backgroundWorker.ProgressChanged += BackgroundWorker_ProgressChanged;
//註冊完成時的回撥
backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
//設定允許任務取消
backgroundWorker.WorkerSupportsCancellation = true;
//設定允許報告進度
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.RunWorkerAsync();
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
//取消任務
if (backgroundWorker.IsBusy)
backgroundWorker.CancelAsync();
}
private void BackgroundWorker_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)
{
//完成時回撥
MessageBox.Show("BackgroundWorker RunWorkerCompleted");
}
private void BackgroundWorker_ProgressChanged(object? sender, ProgressChangedEventArgs e)
{
//報告進度
this.textbox.Text = e.ProgressPercentage.ToString();
}
private void BackgroundWorker_DoWork(object? sender, DoWorkEventArgs e)
{
BackgroundWorker? worker = sender as BackgroundWorker;
if (worker != null)
{
for (int i = 0; i < 20; i++)
{
if (worker.CancellationPending)
{
e.Cancel = true;
break;
}
worker.ReportProgress(i);
Thread.Sleep(100);
}
}
}
建議使用WinForm和WPF的開發人員使用 BackgroundWorker
。
ThreadPool
相對於 Thread
來說具有很多優勢,但是 ThreadPool
在使用上卻存在一定的不方便。比如:
ThreadPool
不支援執行緒的取消、完成、失敗通知等互動性操作。ThreadPool
不支援執行緒執行的先後次序。所以隨著 Task
類及其所提供的非同步程式設計模型的引入,Task
相較ThreadPool
具有更多的優勢。大概有一下幾點:
Task是.NET Framework的一部分,它提供了更高階別的抽象來表示非同步操作或並行任務。相比之下,ThreadPool較為底層,需要手動管理執行緒池和任務佇列。通過使用Task,我們可以以更簡潔、更可讀的方式表達並行邏輯,而無需關注底層執行緒管理的細節。
Task是基於Task Parallel Library(TPL)構建的核心元件,它提供了強大的非同步程式設計支援。利用Task,我們能夠輕鬆定義非同步方法、等待非同步操作完成以及處理任務結果。與此相反,ThreadPool主要用於執行委託或操作,缺乏直接的非同步程式設計功能。
Task在底層使用ThreadPool來執行任務,但它提供了更優秀的效能和資源管理機制。通過使用Task,我們可以利用TPL提供的任務排程器,智慧化地管理執行緒池的大小、工作竊取演演算法和任務優先順序。這樣一來,我們能夠更有效地利用系統資源,並獲得更好的效能表現。
Task擁有強大的任務關聯和組合功能。我們可以使用Task的 ContinueWith()
、 When()
、WhenAll()
、Wait()
等方法定義任務之間的依賴關係,以及在不同任務完成後執行的操作。這種任務組合方式使並行程式設計更加靈活且易於管理。
Task提供了更好的例外處理和取消支援機制。我們可以利用Task的例外處理機制捕獲和處理任務中的異常,而不會導致整個應用程式崩潰。此外,Task還引入 CancellationToken
的概念,可用於取消任務的執行,從而更好地控制並行操作。
所以,儘管ThreadPool在某些情況下仍然有其用途,但在C#程式設計中,使用Task替代ThreadPool已變為通用實踐,推薦優先考慮使用Task來處理並行任務。
參考
編寫高質量程式碼:改善C#程式的157個建議 / 陸敏技著.一北京:機械工業出版社,2011.9
作者: Niuery Daily
出處: https://www.cnblogs.com/pandefu/>
關於作者:.Net Framework,.Net Core ,WindowsForm,WPF ,控制元件庫,多執行緒
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出 原文連結,否則保留追究法律責任的權利。 如有問題, 可郵件諮詢。