Abort()方法可以用來終止當前線程。不論何種情況下你想終止線程,比如線程執行了太長時間或者用戶取消了之前的決定,Abort()方法都很重要。在一個花費很長時間的搜索進程中你可能想使用這個方法。一個搜索引擎可能在繼續運行但是用戶已經看到了他們想要的結果,所以用戶會終止搜索引擎所運行的線程。當在一個線程外調用Abort()方法時,會引發一個ThreadAbortException異常。如果線程代碼中沒有捕獲這個異常,那么線程將會終止。在為一個可能被多線程上下文訪問的方法寫異常處理代碼時要多考慮一下,比如該使用Catch(ThreadAbortException)的地方不要使用Catch(Exception), 前者屬於特定異常且發生后可能不想再恢復,后者屬於通用異常。就我們看來,ThreadAbortException 不是很容易停止,你的程序流可能不會按照你所期待的那樣繼續。
我們來看一個例子。創建一個新工程Destroying,把之前的素數生成代碼拷貝到新的Form1.cs中,然后在界面上添加一個Stop按鈕。
在Stop按鈕點擊事件中添加如下代碼:
private void cmdStop_Click(object sender, EventArgs e) { //Enable the Start button and disable all others. cmdStop.Enabled = false; cmdPause.Enabled = false; cmdResume.Enabled = false; cmdStart.Enabled = true; //Destroy the thread. primeNumberThread.Abort(); }
這個例子與之前的很像。唯一的不同的是當用戶單擊Stop按鈕時我們使用Abort()方法來終止線程。然后我們啟用開始按鈕並禁用所有其他按鈕。你可能已經注意到ThreadAbortException是一個特別的異常。與所有其他可以捕獲的異常一樣,一旦異常代碼塊完成,異常將會被自動重新拋出。當異常被拋出以后,運行時在殺死線程之前運行所有的最終代碼塊。
線程等待(關聯)
Join()方法會阻塞一個給定的線程直到當前線程終止。當我們自一個給定的線程實例上調用Join()方法時,線程將會進入WaitSleepJoin狀態。如果一個線程依賴另外一個線程的話,那么這個方法非常重要。通過簡單地將兩個線程關聯我們可以說正在運行的線程會進入WaitSleepJoin狀態且不會回到運行狀態除非調用Join()方法的線程完成了自己的任務。這聽起來可能有點讓人難與理解,但是讓我們通過一個例子來簡要說明一下這個問題,thread_joining.cs:
/************************************* /* Copyright (c) 2012 Daniel Dong * * Author:Daniel Dong * Blog: www.cnblogs.com/danielWise * Email: guofoo@163.com * */ using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace SimpleThread { public class JoiningThread { static Thread SecondThread; static Thread FirstThread; static void First() { for (int i = 1; i <= 250; i++) { Console.Write(i + " "); } } static void Second() { FirstThread.Join(); for (int i = 251; i <= 500; i++) { Console.Write(i + " "); } } public static void Main() { FirstThread = new Thread(new ThreadStart(First)); SecondThread = new Thread(new ThreadStart(Second)); FirstThread.Start(); SecondThread.Start(); Console.ReadLine(); } } }
這個例子的目的是向控制台順序地輸出數字,從1到500. First()方法將會輸出前250個數字而Second()方法將會輸出后250個。如果不在Second()方法中加FirstThread.Join()的話,執行上下文將會在兩個方法之間來回切換,而我們的輸出會很亂(試着將這行代碼注釋掉,然后重新運行一次)。通過Second()方法中調用FirstThread.Join()方法,Second()方法的執行會暫停直到FirstThread執行完。
Join()方法是重載的;它可以接受一個整型數或者一個TimeSpan類型值作為唯一的參數並返回一個布爾型值。調用這兩個重載方法的任何一個的效果是線程會阻塞直到另外一個線程完成或者等待時間超時,哪個先發生哪個就起作用。如果線程已經完成那么返回值是true 否則 是false.
為什么不對所有方法使用線程?
我們已經看了是用線程的幾個非常有用的優勢;我們可以讓多個線程同時運行,可以讓一個進程內運行多個線程。有這么多好處,為什么我們不對所有方法都使用線程?這樣的話難道不會讓所有代碼都執行地更快?不完全是。事實上,我們將在這部分看到過度使用線程而導致相反的結果。
多線程應用程序需要資源。線程需要內存來存儲線程本地存儲。你可以想象,線程數量被可用內存總量所限制。內存近來已經不那么貴了,所以很多計算機內部都有大容量內存。然而,你不可做出內存可以滿足任意數量線程需求的假設。如果你的程序運行在一個未知的硬件環境下,你不能假設你的程序將有足夠的內存。另外,你也不能假設你的進程將是唯一的使用系統資源的線程。就算計算機有很多內存,那也不意味着它全是為你的程序准備的。
你將發現線程也會增加額外的處理器負擔。在你的程序中創建過多的線程將限制你的線程執行時間。因為你的處理器可能花費更多時間在線程上下文切換上而不是執行你的線程的指令上。如果你的應用程序創建過多線程,你的應用程序將會比所有其他有相對少線程數的應用程序獲得更多執行時間。
為了讓這個概念更易於理解,我們舉個當地百貨商店的並發例子。兩個收銀員在為顧客掃描商品。然而,只有一個裝袋工,他要在兩個收銀員之間來回切換。裝袋工在兩台收銀機之前來回很快的包裝商品,因為商品的堆放速度沒有裝袋工包裝商品的速度快。然而,如果又有兩個收銀員開了新窗口的話,很明顯裝袋工將會花費更多時間在收銀機之間來回跑動而不是包裝商品。最終,商店不得不再請一個裝袋工。這種情況下把收銀員想象成應用程序-或者是線程,把裝袋工當做處理器。處理器需要在不同線程間切換。由於線程數量增加,雜貨店不得不增加一個處理器來保證用戶能夠有耐心等待。
“很多線程”是一個通用術語。一個系統上的“很多”到底指代多少並不是確定的。由於硬件配置很大程度上影響一個系統可以運行的線程數量,“很多”就變成一個不確定的值同時也沒有特定配置細節和測試信息。
基於這些原因微軟建議在應用程序中使用盡量少的線程。這將減少對操作系統資源的要求。
下一篇將介紹使用線程的優勢…