當一個線程直到收到另一個線程的通知才執行相關的動作,這時候,就可以考慮使用"事件等待句柄(Event Wait Handles)"。使用"事件等待句柄"主要用到3個類: AutoResetEvent, ManualResetEvent以及CountdownEvent(.NET 4.0以後才有)。本篇包括:
※ 一個線程等待另一個線程的通知
※ 2個線程互相通知等待
※ 一個線程等待隊列中的多個任務通知
※ 手動控制線程的數量
□ 一個線程等待另一個線程的通知
最簡單的情景是:發出信號的線程只發出一次通知,等待的線程收到通知也只做一次事情。等待的線程肯定有一個等待方法,發出信號的線程必須有一個發出信號的方法,AutoResetEvent類提供了相關方法。
class Program{//true表示將初始狀態設置為終止狀態static EventWaitHandle _wait = new AutoResetEvent(false);static void Main(string[] args){new Thread(Waiter).Start();Thread.Sleep(1000);_wait.Set();//發出指示}static void Waiter(){Console.WriteLine("一切准備就緒,等待指示!");_wait.WaitOne();Console.WriteLine("收到指示~~");}}
○ AutoResetEvent就像地鐵入口的十字轉門,有票插入,就讓進,而且每次只讓一個人進。
○ 當調用WaitOne方法,表示該線程已被阻塞,正在等待信號,就像十字轉門旁等待進入的乘客。
○ 當調用Set方法,表示發出信號給等待線程,就像十字轉門收到車票,乘客可以通過。
關於AutoResetEvent:
○ 還可通過這種方式創建AutoResetEvent實例:var auto = new EventWaitHandle(false, EventResetMode.AutoReset);
○ 如果調用了Set方法,卻沒有其它線程調用WaitOne方法,這個handle會一直存在
○ 如果調用Set方法多次,卻有多個線程調用WaitOne方法,也只能讓這些線程挨個接收信號,即每次只有一個線程接收信號
○ WaitOne還有幾個接收時間間隔參數的重載方法,使用WaitOne(0)可以測試一個wait handle是否已經打開
○ GC自動回收wait handles
□ 2個線程互相通知等待
還有一種情形:發出信號的線程要發出多次通知,每一次需要確認等待線程收到後再發下一個通知。大概的過程就是:線程A第一次做事並發出通知,進入等待狀態;線程B收到通知,發出通知,通知線程A,線程B進入等待狀態;線程A收到線程B的通知,第二次做事並發出通知,進入等待狀態......2個線程互相通知,每個線程既是發出信號者,也是等待者。借助AutoResetEvent類可以解決此需求。
class Program{static EventWaitHandle _ready = new AutoResetEvent(false);static EventWaitHandle _go = new AutoResetEvent(false);static readonly object o = new object();private static string _msg;static void Main(string[] args){new Thread(DoSth).Start();//第一次等待直到另外一個線程准備好_ready.WaitOne();lock (o){_msg = "你好";}_go.Set();//第二次等待_ready.WaitOne();lock (o){_msg = "嗎";}_go.Set();//第三次_ready.WaitOne();lock (o){_msg = null;}_go.Set();}static void DoSth(){while (true){_ready.Set();_go.WaitOne();lock (o){if(_msg == null) return;Console.WriteLine(_msg);}}}}
把Main方法中的線程稱為主線程,把另一個線程稱為工作線程,2個線程是這樣工作的:
→主線程使用WaitOne方法第一次等待,說:“工作線程,我等在這裡”
→工作線程使用Set方法,說:“主線程,我給你信號,你准備第一條信息吧”,並且又使用WaitOne方法讓自己等待,就說:“主線程,我給你信號了,我等在這裡,准備接收你的第一條信息”,再看看暫時還沒有需要顯示的信息,於是作罷
→主線程收到工作線程的信號,設置第一條信息,然後使用Set方法,說"工作線程,我的第一條信息給你,給你信號",並且又使用WaitOne方法讓自己第二次等待,說:"工作線程,我給你信號了,我等在這裡"
→工作線程又使用Set方法,說:“主線程,我給你信號,你去准備第二條信息吧”,並且又使用WaitOne方法讓自己等待,就說:“主線程,我已經給你信號了,我等在這裡,准備接收你的第二條信息”,再看看這時有需要顯示的信息,就把信息打印了出來
→依次類推
□ 一個線程等待隊列中的多個任務通知
當一個等待的線程,需要逐個執行多個任務,就可以把任務放在隊列中。
通常把能實現實現上述需求的叫做"生產/消費隊列"。所謂的"生產"是指能把多個任務放到隊列中,所謂"消費"是指當任務逐一出列,再執行該任務。
class ProducerConsumerQueue : IDisposable{EventWaitHandle _ewh = new AutoResetEvent(false);private Thread _worker; //等待線程private readonly object _locker = new object();Queue<string> _tasks = new Queue<string>();//任務隊列public ProducerConsumerQueue(){_worker = new Thread(Work);_worker.Start();}//任務進入隊列public void EnqueueTask(string task){lock (_locker){_tasks.Enqueue(task);}//任務一旦進入隊列就發出信號_ewh.Set();}void Work(){while (true){//從隊列中獲取taskstring task = null;lock (_locker){if (_tasks.Count > 0){task = _tasks.Dequeue();if(task == null) return;}}//如果task不為null,模擬執行taskif (task != null){Console.WriteLine("正在執行線程任務 " + task);Thread.Sleep(1000); //模擬線程執行的過程}else//如果taks為null{_ewh.WaitOne();//等待信號}}}public void Dispose(){EnqueueTask(null); //發出信號讓消費線程退出_worker.Join();//讓消費線程借宿_ewh.Close();//釋放event wait handle}}
○ EnqueueTask方法,讓任務進入隊列,每個進入隊列的任務使用Set方法發出通知,產生任務的過程就是所謂的"生產"
○ Wokr方法,在沒有task的時候,使用WaitOne方法一直等待;當任務出列,就執行任務,執行任務的過程就是所謂的"消費"
○ 構造函數創建、啟動等待線程,讓等待線程一直工作者(通過無限循環)
客戶端調用。
class Program{static void Main(string[] args){using (ProducerConsumerQueue q = new ProducerConsumerQueue()){q.EnqueueTask("hello");for (int i = 0; i < 3; i++){q.EnqueueTask("報數" + i);}q.EnqueueTask("world");}}}
□ 手動控制線程的數量
■ 使用ManualResetEvent
如果把AutoResetEvent比作地鐵入口的十字轉門,一次只能允許一個人進入;ManualResetEvent可看作公司門衛,上班時間到,打開門可以讓多人進入。ManualResetEvent的Set方法就如同開門,任意多個線程可以進入,Reset方法如同關門,線程從此不能再進入。
創建ManualResetEvent實例有2種方式:
var manual1 = new ManualResetEvent (false);var manual2 = new EventWaitHandle (false, EventResetMode.ManualReset);
以下是EventWaitHandle的一個簡單應用:
class Program{static EventWaitHandle handle = new ManualResetEvent(false);static void Main(string[] args){handle.Set();new Thread(SaySth).Start("Hello");new Thread(SaySth).Start("World");Thread.Sleep(2000);handle.Reset();new Thread(SaySth).Start("Again");}static void SaySth(object data){handle.WaitOne();Console.WriteLine("我想說的是:" + data);}}
○ Set方法,相當於開門,其後面的2個線程有效
○ Reset方法,相當於關門,其後面的1個線程無效
■ 使用CountdownEvent
CountdownEvent也可以看作公司門衛,只不過,上班時間到,規定只允許若干個人進去。
class Program{static CountdownEvent _countdown = new CountdownEvent(2);static void Main(string[] args){new Thread(SaySth).Start("1");new Thread(SaySth).Start("2");}static void SaySth(object o){Thread.Sleep(1000);Console.WriteLine(o);_countdown.Signal();}}
○ 在CountdownEvent的構造函數中設置允許的最大線程數
○ Signal方法表示計數一次
總結:○ 使用AutoResetEvent類,可以讓一個線程等待另一個線程的通知,2個線程互相通知等待,一個線程等待隊列中的多個任務通知
○ 使用ManualResetEvent類,手動控制任意多的線程數量
○ CountdownEvent類,手動控制固定數量的線程數量
線程系列包括:
線程系列09,線程的等待、通知,以及手動控制線程數量
我的理解這是一個線程間通訊的問題,接收到a的時候主線程創建一個新的線程,運行的函數(比如叫WorkRun方法)在新線程(比如叫thA)中運行,當主線程接收到b時需要通知thA使他中止或改變其運行邏輯。
啟動線程的寫法:
Thread thA = new Thread(new ThreadStart(WorkRun));
thA.Start();
事實上,只有兩種可能的場景,第一種是主線程向thA發送中止線程指令,讓函數中止執行;另一種是函數在執行到某一個點的時候停下來等待主線程給他一個信號來決定該執行怎樣的邏輯。
1. 如果是中止線程的話,只需要在接收到b的時候寫
if(thA.ThreadState == ThreadState.Running)
{
thA.Abort();
}
2. 如果需要在運行中停下來等待主線程指令的話,使用ManualResetEvent,ManualResetEvent 允許線程通過發信號互相通信。通常,此通信涉及一個線程在其他線程進行之前必須完成的任務。
當一個線程開始一個活動(此活動必須完成後,其他線程才能開始)時,它調用 Reset 以將 ManualResetEvent 置於非終止狀態。此線程可被視為控制 ManualResetEvent。調用 ManualResetEvent 上的 WaitOne 的線程將阻止,並等待信號。當控制線程完成活動時,它調用 Set 以發出等待線程可以繼續進行的信號。並釋放所有等待線程。
一旦它被終止,ManualResetEvent 將保持終止狀態,直到它被手動重置。即對 WaitOne 的調用將立即返回。
可以通過將布爾值傳遞給構造函數來控制 ManualResetEvent 的初始狀態,如果初始狀態處於終止狀態,為 true;否則為 false。
ManualResetEvent 也可以同 staticWaitAll 和 WaitAny 方法一起使用。
以下為實例(下面的代碼示例闡釋了如何使用等待句柄來發送復雜數字計算的不同階段的完成信號。計算的格式為:結果 = 第一項 + 第二項 + 第三項,其中每一項都要求使用計算出的基數進行預計算和最終計算。):
using System;
using System.Threading;
class CalculateTest
{
static void Main()
{
Calculate calc = new Calculate();
Console.WriteLine("Result = {0}.",
calc.Result(234).ToString());
Console.WriteLine("Result = {0}.",
calc.Result(55).ToString());
}
}
class Calculate
{
double baseNumber, firstTerm, secondTerm, thirdTerm;
AutoResetEvent[] autoEvents;
ManualResetEvent manualEvent;
// Generate random numbers to simulate the actual calculations.
Random rand......余下全文>>
一、線程的概念
一般來說,我們把正在計算機中執行的程序叫做"進程"(Process) ,而不將其稱為程序(Program)。所謂"線程"(Thread),是"進程"中某個單一順序的控制流。
新興的操作系統,如Mac,Windows NT,Windows 95等,大多采用多線程的概念,把線程視為基本執行單位。線程也是Java中的相當重要的組成部分之一。
甚至最簡單的Applet也是由多個線程來完成的。在Java中,任何一個Applet的paint()和update()方法都是由AWT(Abstract Window Toolkit)繪圖與事件處理線程調用的,而Applet 主要的裡程碑方法——init(),start(),stop()和destory() ——是由執行該Applet的應用調用的。
單線程的概念沒有什麼新的地方,真正有趣的是在一個程序中同時使用多個線程來完成不同的任務。某些地方用輕量進程(Lightweig ht Process)來代替線程,線程與真正進程的相似性在於它們都是單一順序控制流。然而線程被認為輕量是由於它運行於整個程序的上下文內,能使用整個程序共有的資源和程序環境。
作為單一順序控制流,在運行的程序內線程必須擁有一些資源作為必要的開銷。例如,必須有執行堆棧和程序計數器。在線程內執行的代碼只在它的上下文中起作用,因此某些地方用"執行上下文"來代替"線程"。
二、線程屬性
為了正確有效地使用線程,必須理解線程的各個方面並了解Java 實時系統。必須知道如何提供線程體、線程的生命周期、實時系統如 何調度線程、線程組、什麼是幽靈線程(Demo nThread)。
(1)線程體
所有的操作都發生在線程體中,在Java中線程體是從Thread類繼承的run()方法,或實現Runnable接口的類中的run()方法。當線程產生並初始化後,實時系統調用它的run()方法。run()方法內的代碼實現所產生線程的行為,它是線程的主要部分。
(2)線程狀態
附圖表示了線程在它的生命周期內的任何時刻所能處的狀態以及引起狀態改變的方法。這圖並不是完整的有限狀態圖,但基本概括了線程中比較感興趣和普遍的方面。以下討論有關線程生命周期以此為據。
●新線程態(New Thread)
產生一個Thread對象就生成一個新線程。當線程處於"新線程"狀態時,僅僅是一個空線程對象,它還沒有分配到系統資源。因此只能啟動或終止它。任何其他操作都會引發異常。
●可運行態(Runnable)
start()方法產生運行線程所必須的資源,調度線程執行,並且調用線程的run()方法。在這時線程處於可運行態。該狀態不稱為運行態是因為這時的線程並不總是一直占用處理機。特別是對於只有一個處理機的PC而言,任何時刻只能有一個處於可運行態的線程占用處理 機。Java通過調度來實現多線程對處理機的共享。
●非運行態(Not Runnable)
當以下事件發生時,線程進入非運行態。
①suspend()方法被調用;
②sleep()方法被調用;
③線程使用wait()來等待條件變量;
④線程處於I/O等待。
●死亡態(Dead)
當run()方法返回,或別的線程調用s......余下全文>>