Net线程间通信的异步机制
線程間通信
我們看下面的圖
圖1
我們來看線程間通信的原理:線程(Thread B)和線程(Thread A)通信, 首先線程A 必須實(shí)現(xiàn)同步上下文對象(Synchronization Context), 線程B通過調(diào)用線程A的同步上下文對象來訪問線程A,所有實(shí)現(xiàn)都是在同步上下文中完成的.線程B有兩種方式來實(shí)現(xiàn)線程間的通信.
第一種:調(diào)用線程A的同步上下文對象,阻礙當(dāng)前線程,執(zhí)行紅色箭頭調(diào)用,直到黃色箭頭返回(同步上下文執(zhí)行完畢)才釋放當(dāng)前線程. (1->2->3->5)
第二種: 調(diào)用線程A的同步上下文對象(實(shí)際上是在開啟一個新線程去執(zhí)行,1->2->3->5) ,執(zhí)行紅色箭頭,但并不阻礙當(dāng)前線程(原有線程,1->4->5),綠色箭頭繼續(xù)執(zhí)行.
文章中將會通過下面幾個類來進(jìn)行介紹:
ISynchronizeInvoke 接口
SynchronizationContext 類
AsyncOperation / AsyncOperationManager 類
1. ISynchronizeInvoke 接口
我們先來看下面一段異步的代碼(Window Form控件下有1個Button/1個Label),但點(diǎn)擊Button的時候,執(zhí)行異步調(diào)用,完成后,告訴Window Form的 Label控件Text屬性” Asynchronous End”.
Code1.1
delegate void DoWork();private void button1_Click(object sender, EventArgs e){//輔助方法,查看當(dāng)前線程Debug.WriteLine(string.Format("Window Form Method.Thread ID:#{0}",Thread.CurrentThread.ManagedThreadId));//Label lblStatus 屬于主線程的控件[1]this.lblStatus.Text = "Asynchronous Start.";//使用委托來調(diào)用異步方法DoWork work = DoWorkMethod;work.BeginInvoke(OnWorkCallback, work);}void OnWorkCallback(IAsyncResult asyncResult){//輔助方法,查看當(dāng)前線程Debug.WriteLine(string.Format("Asynchronous Callback Method.Thread ID:#{0}",Thread.CurrentThread.ManagedThreadId));DoWork work = asyncResult.AsyncState as DoWork;if (work != null){work.EndInvoke(asyncResult);}// 報(bào)錯:"線程間操作無效: 從不是創(chuàng)建控件“l(fā)blStatus”的線程訪問它."this.lblStatus.Text = "Asynchronous End"; //上面注釋[1] }void DoWorkMethod(){Thread.Sleep(3000);//模擬耗時工作}?運(yùn)行代碼,我們在第22行報(bào)錯(異步方法體內(nèi)).為什么呢?我們必須清楚的一點(diǎn),在windows應(yīng)用窗體應(yīng)用程序中,對窗體上控件屬性的任何修改都必須在主線程中完成。不能從其他線程安全地訪問控件的方法和屬性。從Debug窗口中我們也可以看出(圖1.1).執(zhí)行Button Click事件的時候,運(yùn)行在線程ID =#10; 在異步的方法體內(nèi),運(yùn)行在線程ID=#7.不同線程間不能直接通信.
為了解決這個問題,實(shí)現(xiàn)圖1.1 中 #10 和 #7 的通信,下來開始認(rèn)識ISynchronizeInvoke 接口(此接口來自.Net Framework 1.0),提供3個方法1個屬性:
BeginInvoke / EndInvoke 方法 : 異步方法
Invoke 方法 : 同步方法
InvokeRequired 屬性 : 判讀來源的執(zhí)行線程
下面我們看Code1.2的具體代碼來說明(對Code1.1改寫,其中Label 改為ListBox)
delegate void DoWork();private void button1_Click(object sender, EventArgs e){//更新狀態(tài),添加到Listbox 中AddValue("Asynchronous Start.");//使用委托來調(diào)用異步方法DoWork work = DoWorkMethod;work.BeginInvoke(OnWorkCallback, work);}void OnWorkCallback(IAsyncResult asyncResult){DoWork work = asyncResult.AsyncState as DoWork;if (work != null){work.EndInvoke(asyncResult);}//(1)方法:調(diào)用Control控件的Invoke//Action<string> asyncUpdateState = UpdateStatus; //Action<string> 介紹=> 附1//Invoke(asyncUpdateState, "1:Asynchronous End.");//(2)方法:直接在異步調(diào)用的線程下UpdateStatus("2:Asynchronous End.");}void UpdateStatus(string input){//把你需要通知的控件Control 賦值給ISynchronizeInvoke//來實(shí)現(xiàn)線程間的通信ISynchronizeInvoke async = this.listBoxStatus;//使用(1)方法,InvokeRequired == false ,來源當(dāng)前(Window Form)主線程if (async.InvokeRequired == false)AddValue(input);else// 使用(2)方法 == true ,來源其他線程(異步) { Action<string> action = new Action<string>(status =>{AddValue(status);});//調(diào)用ISynchronizeInvoke 提供的Invoke 同步方法,阻礙線程,直到調(diào)用結(jié)束//也可以使用ISynchronizeInvoke 提供的異步BeginInvoke/EndInvoke方法來實(shí)現(xiàn)調(diào)用.async.Invoke(action, new object[] { input });}}void AddValue(string input){this.listBoxStatus.Items.Add(string.Format("[(#{2}){0}]Context is null:{1}", input,Thread.CurrentContext==null, Thread.CurrentThread.ManagedThreadId));}void DoWorkMethod(){Thread.Sleep(3000);//模擬耗時工作}圖1.2
在代碼中(UpdateStatus方法體內(nèi)),我們可以看到主要是在ISynchronizeInvoke async = this.listBoxStatus;實(shí)現(xiàn)了線程間的通信,MSDN的解釋” 實(shí)現(xiàn)此接口的對象可以接收事件已發(fā)生的通知,并且可以響應(yīng)有關(guān)該事件的查詢”. 并使Window Form(主線程) 下的ListBox 控件和來自異步方法(另外一個線程)的建立了通道. InvokeRequired 判斷線程的來源,如果使用(1)方法,來源于Window Form 自身Control 的Invoke方法, InvokeRequired將返回false; 來源另外線程(異步)如果使用(2)返回true.同時ISynchronizeInvoke 提供了異步(BeginInvoke+EndInvok)和同步方法(Invoke)來實(shí)現(xiàn)線程間通信.Invoke 就是最上面的圖1 所示的第一種 / BeginInvoke+EndInvok 是第二種.
附1:關(guān)于Action<T…> / Func (T…, TResult) (簡單的說就是”簡化后的委托”)的知識可以看MSDN的介紹.
Action<T…>:?http://msdn.microsoft.com/zh-cn/library/018hxwa8.aspx
Func (T…, TResult):?http://msdn.microsoft.com/zh-cn/library/bb549151.aspx
Code1.2雖然實(shí)現(xiàn)了線程間的通信, 回顧圖1的解釋,” 首先線程A 必須實(shí)現(xiàn)同步上下文對象(Synchronization Context)”, 而在Code1.2 中并沒有為Window Form(主線程)實(shí)現(xiàn)上下文對象,如果沒有這個對象一切都是不成立的.那么Window Form 做了些什么呢?
我們來看下面的代碼(使用Console程序):
Code1.3
static class Program{static void Main(){//1,在Main 主線程中運(yùn)行,查看線程ID和同步上下文Console.WriteLine("0.ThreadID:#{1},Synchronization Context is null?{0}",SynchronizationContext.Current == null, Thread.CurrentThread.ManagedThreadId);//2,在Main 主線程中運(yùn)行,實(shí)例化空對象Test,查看線程ID和同步上下文Test a = new Test();Console.WriteLine("1.ThreadID:#{1},Synchronization Context is null?{0}",SynchronizationContext.Current == null, Thread.CurrentThread.ManagedThreadId);//3,在Main 主線程中運(yùn)行,實(shí)例化FormTest對象(繼承Form),查看線程ID和同步上下文FormTest test = new FormTest();Console.WriteLine("2.ThreadID:#{1},Synchronization Context is null?{0}",SynchronizationContext.Current == null, Thread.CurrentThread.ManagedThreadId);//4,在新線程中運(yùn)行,查看線程ID和同步上下文new Thread(work).Start();Console.Read();}static void work(){Console.WriteLine("3.ThreadID:#{1},Synchronization Context is null?{0}",SynchronizationContext.Current == null, Thread.CurrentThread.ManagedThreadId);}}public class FormTest : System.Windows.Forms.Form { }public class Test { }圖1.3
由代碼和圖可以看出(SynchronizationContext.Current == null 判斷同步上下文對象是否存在), 實(shí)例化FormTest 對象后(繼承System.Windows.Forms.Form),Form默認(rèn)的幫我們創(chuàng)建了同步上下文對象,使主線程#9 具備了同步上下文對象,這就是為什么Code1.2 不用聲明同步上下文對象的原因,同時也告訴我們,開啟一個新線程#10,線程本身是沒有同步上下文對象的.
2. SynchronizationContext 類
相比ISynchronizeInvoke 接口,SynchronizationContext 類(來自.Net Framework 2.0)提供了更多的方法來操作同步上下文對象,實(shí)現(xiàn)線程間通信.在上面的例子中SynchronizationContext類中將由 Post/Send 方法來實(shí)現(xiàn).
反編譯后我們看到:
Code2.1
public virtual void Post(SendOrPostCallback d, object state){ThreadPool.QueueUserWorkItem(new WaitCallback(d.Invoke), state);}public virtual void Send(SendOrPostCallback d, object state){d(state);}Send = ISynchronizeInvoke 中的Invoke 同步調(diào)用.圖1中的第一種
Post = ISynchronizeInvoke 中的BeginInvoke + EndInvoke異步調(diào)用. 圖1中的第二種
改寫Code1.2的代碼(還是在WinForm 下編程).
Code2.2
delegate void DoWork();private void button1_Click(object sender, EventArgs e){//System.Windows.Forms.Form 自動的創(chuàng)建默認(rèn)的同步上下文對象,//直接的獲取當(dāng)前的同步上下文對象SynchronizationContext context = SynchronizationContext.Current;//更新狀態(tài),添加到Listbox 中AddValue<string>("Asynchronous Start.");//使用委托來調(diào)用異步方法DoWork work = DoWorkMethod;work.BeginInvoke(OnWorkCallback, context);}void OnWorkCallback(IAsyncResult asyncResult){AsyncResult async = (AsyncResult)asyncResult;DoWork work = (DoWork)async.AsyncDelegate;work.EndInvoke(asyncResult);//更新狀態(tài)UpdateStatus("Asynchronous End.", asyncResult.AsyncState);}void UpdateStatus(object input,object syncContext){//獲取主線程(Window Form)中同步上下文對象SynchronizationContext context = syncContext as SynchronizationContext;//使用SynchronizationContext 類中異步Post 方法SendOrPostCallback callback = new SendOrPostCallback(p => {AddValue<object>(p);});context.Post(callback, input);//Post 為異步,Send 為同步 }void AddValue<T>(T input){this.listBoxStatus.Items.Add(string.Format("[(#{2}){0}]Context is null:{1}", input, Thread.CurrentContext == null, Thread.CurrentThread.ManagedThreadId));}void DoWorkMethod(){Thread.Sleep(3000);//模擬耗時工作}上面我們已經(jīng)說過在主線程中System.Windows.Forms.Form 自動的創(chuàng)建默認(rèn)的同步上下文對象, 這時候我們把當(dāng)前的同步上下文對象通過參數(shù)的形式賦值到異步線程中,調(diào)用Post 方法來實(shí)現(xiàn), Post 方法接收 SendOrPostCallback 委托和額外object state參數(shù),在Post方法體內(nèi)調(diào)用線程池的線程來實(shí)現(xiàn)(Code2.1).當(dāng)然我們也可以直接使用Send方法.
下面我們看看線程中的代碼(在Console 下編程).
Code2.3
static class Program{static void Main(){Output("Main Thread Start.");//為主線程創(chuàng)建Synchronization Contextvar context = new SynchronizationContext();//開始一個新線程Thread threadB = new Thread(work);threadB.Start(context);Console.Read();}static void work(object context){Output("Thread B");//獲取主線程中的同步上下文對象SynchronizationContext sc = context as SynchronizationContext;//異步的方式和主線程通信,并發(fā)送"Hello World".sc.Post(new SendOrPostCallback(p =>{Output(p);}), "Hello World");}static void Output(object value){Console.WriteLine("[ThreadID:#{0}]{1}", Thread.CurrentThread.ManagedThreadId, value);}}圖2.3
在主線程中因?yàn)闆]有同步上下文對象,所以開始我們new SynchronizationContext(); 對象,其他和Code2.2 基本一樣.從圖2.3很好的解釋圖1的第二種調(diào)用,也說明了Post 是開啟新線程(線程池)運(yùn)行的.
3. AsyncOperation / AsyncOperationManager 類
AsyncOperation / AsyncOperationManager 類是SynchronizationContext 類的進(jìn)一步封裝和實(shí)現(xiàn), AsyncOperationManager在創(chuàng)建AsyncOperation對象的時候會取得當(dāng)前線程的同步上下文對象,并存儲在AsyncOperation之中,使我們訪問同步上下文對象更加容易.
Code3.1
public class MySynchronizedClass{private AsyncOperation operation;public event EventHandler somethingHappened;public MySynchronizedClass(){//創(chuàng)建AsyncOperation 對象,并把當(dāng)前線程的同步上下文保持到AsyncOperation中.operation = AsyncOperationManager.CreateOperation(null);Thread workerThread = new Thread(new ThreadStart(DoWork));workerThread.Start();}private void DoWork(){SendOrPostCallback callback = new SendOrPostCallback(state =>{EventHandler handler = somethingHappened;if (handler != null){handler(this, EventArgs.Empty);}});operation.Post(callback, null);//注意1 operation.OperationCompleted();}}代碼很簡單,我也不在解釋,可以參照上面所有代碼. 注意1: AsyncOperation類中實(shí)現(xiàn)了OperationCompleted的方法. SynchronizationContext 類中這個方法是沒有具體的代碼實(shí)現(xiàn)的.
總結(jié):
文章中也非常適合線程的編程(除了異步)來實(shí)現(xiàn)通信, SynchronizationContext是最重要的一個,其他的擴(kuò)展類,如SynchronizationContextSwitcher 等更高級的主題,具體可參考下面的鏈接. 在Winform中有個非常重要的BackgroundWorker 類,關(guān)于BackgroundWorker的文章很多,在此不做解釋了.(http://hi.baidu.com/ldy201001/blog/item/3ea946c4a586f8a08326ac1a.html)
本文轉(zhuǎn)自:http://www.cnblogs.com/cpcpc/archive/2012/07/02/2572711.html
相關(guān):AsyncCallback方法和主線程怎么同步呢?
轉(zhuǎn)載于:https://www.cnblogs.com/jRoger/articles/2574522.html
總結(jié)
以上是生活随笔為你收集整理的Net线程间通信的异步机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 下拉框的属性
- 下一篇: zip unzip 命令
