使用線程能極大地提升用戶體驗度,但是作為開發者應該注意到,線程的開銷是很大的。
線程的空間開銷來自:
1)線程內核對像(Thread Kernel Object)。每個線程都會創建一個這樣的對象,它主要包含線程上下文信息,在32位系統中,它所佔用的內存在700字節左右。
2)線程環境塊(Thread Environment Block)。TEB包括線程的異常處理鏈,32位系統中佔用4KB內存。
3)用戶模式棧(User Mode Stack),即線程棧。線程棧用於保存方法的參數、局部變量和返回值。每個線程棧佔用1024KB的內存。要用完這些內存很簡單,寫一個不能結束的遞歸方法,讓方法參數和返回值不停地消耗內存,很快就會發生OutOfMemoryException。
4)內核模式棧(Kernel Mode Stack)。當調用操作系統的內核模式函數時,系統會將函數參數從用戶模式棧複製到內核模式棧。在32位系統中,內核模式棧會佔用12KB內存。
線程的時間開銷來自:
步驟1 進入內核模式。
步驟2 將上下文信息(主要是一些CPU 寄存器信息)保存到正在執行的線程內核對像上。
步驟3 系統獲取一個 Spinlock,並確定下一個要執行的線程,然後釋放 Spinlock。如果下一個線程不在同一個進程內,則需要進行虛擬地址交換。
步驟4 從將被執行的線程內核對像上載入上下文信息。
步驟5 離開內核模式。
由於要進行如此多的工作,所以創建和銷毀一個線程就意味著代價「昂貴」。為了避免程序員無節制地使用線程,微軟開發了「線程池」技術。簡單來說,線程池就是替開發人員管理工作線程。當一項工作完畢時,CLR不會銷毀這個線程,而是會保留這個線程一段時間,看是否有別的工作需要這個線程。至於何時銷毀或新起線程,由CLR根據自身的算法來做這個決定。所以,如果我們要多線程編碼,不應想到:
應該首先想到依賴線程池:
線程池技術能讓我們重點關注業務的實現,而不是線程的性能測試。
本建議還提到了一個類型BackgroundWorker。BackgroundWorker是在內部使用了線程池的技術;同時,在Winform或WPF編碼中,它還給工作線程和UI線程提供了交互的能力。如果我們稍加注意,就會發現:Thread和ThreadPool默認都沒有提供這種交互能力,而BackgroundWorker卻通過事件提供了這種能力。這種能力包括:報告進度、支持完成回調、取消任務、暫停任務等。一個使用BackgroundWorker的簡單示例如下:
該示例是一個Winform窗體程序,正在從事Winform或WPF開發的人員,可考慮使用BackgroundWorker。