下面的表格列展了.NET對協調或同步線程動作的可用的工具:
簡易阻止方法
構成
目的
Sleep
阻止給定的時間周期
Join
等待另一個線程完成
鎖系統
構成
目的
跨進程?
速度
lock
確保只有一個線程訪問某個資源或某段代碼。
否
快
Mutex
確保只有一個線程訪問某個資源或某段代碼。可被用於防止一個程序的多個實例同時運行。
是
中等
Semaphore
確保不超過指定數目的線程訪問某個資源或某段代碼。
是
中等
信號系統
構成
目的
跨進程?
速度
EventWaitHandle
允許線程等待直到它受到了另一個線程發出信號。
是
中等
Wait 和 Pulse*
允許一個線程等待直到自定義阻止條件得到滿足。
否
中等
阻止(Blocking)
當一個線程通過上面所列的方式處於等待或暫停的狀態,被稱為被阻止。一旦被阻止,線程立刻放棄它被分配的CPU時間,將它的ThreadState屬性添加為WaitSleepJoin狀態,直到停止阻止。停止阻止由以下任一條件觸發:
當線程通過(不建議)Suspend 方法暫停,不認為是被阻止了。
休眠 和 輪詢
調用Thread.Sleep阻止當前的線程指定的時間(或者直到中斷):
static void Main() { Thread.Sleep (0); // 釋放CPU時間片 Thread.Sleep (1000); // 休眠1000毫秒 Thread.Sleep (TimeSpan.FromHours (1)); // 休眠1小時 Thread.Sleep (Timeout.Infinite); // 休眠直到中斷 }
確切地說,Thread.Sleep放棄了占用CPU,請求不再被分配時間直到超過某個給定的時間。Thread.Sleep(0)放棄CPU的時間剛剛夠其它在時間片隊列裡的活動線程(如果有的話)被執行。
Thread.Sleep在阻止方法中是唯一的暫停捕獲Windows Forms程序的Windows消息的方法,在Windows Forms程序中是一個很大的問題,任何對主UI線程的阻止都將使程序失去相應。因此一般避免這樣使用,無論信息獲取是否被“技術地”暫定與否。線程類同時也提供了一個SpinWait方法,它使用輪詢CPU而非放棄CPU時間的方式,保持給定的迭代次數進行“無用地繁忙”。50迭代停頓大約一微秒,一般取決於CPU的速度和負載。從技術上講,SpinWait並不是一個阻止的方法:一個處於spin-waiting的線程的ThreadState不是WaitSleepJoin狀態,並且也不會被其它的線程過早的中斷(Interrupt)。SpinWait很少被使用,它的作用是等待一個在極短時間(可能小於一微秒)內可准備好的可預期的資源,而不用調用Sleep方法阻止線程而浪費CPU時間。不過,這種技術的優勢只有在多處理器計算機:對單一處理器的電腦,直到輪詢的線程結束了它的時間片之前,一個資源沒有機會改變狀態,這有違它的初衷。並且調用SpinWait經常會花費較長的時間這本身就浪費了CPU時間。
阻止 vs. 輪詢
線程可以等待某個確定的條件來明確輪詢使用一個輪詢的方式,比如:
while (!proceed);
或者:
while (DateTime.Now < nextStartTime);
這是非常浪費CPU時間的:對於CLR和操作系統而言,線程進行了一個重要的計算,所以分配了相應的資源!在這種狀態
下的輪詢線程不算是阻止,不像一個線程等待一個EventWaitHandle(一般使用這樣的信號任務來構建)。
阻止和輪詢組合使用可以產生一些變換:
while (!proceed) Thread.Sleep (x); // "輪詢休眠!"
x越大,CPU效率越高,折中方案是增大潛伏時間,任何20ms的花費是微不足道的,除非循環中的條件是極其復雜的。
除了稍有延遲,這種輪詢和休眠的方式可以結合的非常好,可能它最大的用處在於程序員可以放棄使用復雜的信號結構來工作了。
使用Join等待一個線程完成
可以通過Join方法阻止線程直到另一個線程結束:
class ThreadDemo { static void Main() { Thread t = new Thread (delegate() { Console.ReadLine();}); t.Start(); t.Join(); // 等待直到線程完成 Console.WriteLine ("Thread t's ReadLine complete!"); } }
Join方法也接收一個使用毫秒或用TimeSpan類的超時參數,當Join超時是返回false,如果線程已終止,則返回true 。
Join所帶的超時參數非常像Sleep方法,實際上下面兩行代碼幾乎差不多:
Thread.Sleep (1000);
Thread.CurrentThread.Join (1000);
他們的區別明顯在於單線程的應用程序域與COM互操作性:在阻止時,Join保持信息捕獲,Sleep暫停信息捕獲。