異步編程:異步編程模型 (APM)
異步編程模型 (APM)是.NET1.0的時(shí)候就已經(jīng)推出的古老異步編程模式,此模式基于IAsyncResult接口實(shí)現(xiàn)。
隨著技術(shù)的發(fā)展,又在“.NET1.0異步編程模型 (APM)”之后推出了“.NET2.0基于事件的編程模式”及“.NET4.X基于任務(wù)的編程模式”兩種異步編程模式。盡管在新的設(shè)計(jì)上我們推薦都使用“.NET4.0基于任務(wù)的編程模式”,但我還是計(jì)劃整理出舊版的異步編程模型,因?yàn)椋?/p>
1. 在一些特殊場(chǎng)合下我們可能覺(jué)得一種模式更適合;
2. 可以更充分認(rèn)識(shí)三種模式之間的優(yōu)劣,便于選擇;
3. 很多遺留的代碼包含了舊的設(shè)計(jì)模式;
4. 等等…
示例下載:異步編程:IAsyncResult異步編程模型.rar
IAsyncResult設(shè)計(jì)模式----規(guī)范概述
使用IAsyncResult設(shè)計(jì)模式的異步操作是通過(guò)名為 Begin*** 和 End*** 的兩個(gè)方法來(lái)實(shí)現(xiàn)的,這兩個(gè)方法分別指代開(kāi)始和結(jié)束異步操作。例如,F(xiàn)ileStream類(lèi)提供BeginRead和EndRead方法來(lái)從文件異步讀取字節(jié)。這兩個(gè)方法實(shí)現(xiàn)了 Read 方法的異步版本。
在調(diào)用 Begin*** 后,應(yīng)用程序可以繼續(xù)在調(diào)用線(xiàn)程上執(zhí)行指令,同時(shí)異步操作在另一個(gè)線(xiàn)程上執(zhí)行。(如果有返回值還應(yīng)調(diào)用 End*** 來(lái)獲取操作的結(jié)果)。
1) Begin***
a) Begin*** 方法帶有該方法的同步版本簽名中聲明的任何參數(shù)。
b) Begin*** 方法簽名中不包含任何輸出參數(shù)。方法簽名最后兩個(gè)參數(shù)的規(guī)范是:第一個(gè)參數(shù)定義一個(gè)AsyncCallback委托,此委托引用在異步操作完成時(shí)調(diào)用的方法。第二個(gè)參數(shù)是一個(gè)用戶(hù)定義的對(duì)象。此對(duì)象可用來(lái)向異步操作完成時(shí)為AsyncCallback委托方法傳遞應(yīng)用程序特定的狀態(tài)信息(eg:可通過(guò)此對(duì)象在委托中訪(fǎng)問(wèn)End*** 方法)。另外,這兩個(gè)參數(shù)都可以傳遞null。
c) 返回IAsyncResult對(duì)象。
- // 表示異步操作的狀態(tài)。
- [ComVisible(true)]
- public interface IAsyncResult
- {
- // 獲取用戶(hù)定義的對(duì)象,它限定或包含關(guān)于異步操作的信息。
- object AsyncState { get; }
- // 獲取用于等待異步操作完成的System.Threading.WaitHandle,待異步操作完成時(shí)獲得信號(hào)。
- WaitHandle AsyncWaitHandle { get; }
- // 獲取一個(gè)值,該值指示異步操作是否同步完成。
- bool CompletedSynchronously { get; }
- // 獲取一個(gè)值,該值指示異步操作是否已完成。
- bool IsCompleted { get; }
- }
- // 常用委托聲明(我后面示例是使用了自定義的帶ref參數(shù)的委托)
- public delegate void AsyncCallback(IAsyncResult ar)
2) End***
a) End*** 方法可結(jié)束異步操作,如果調(diào)用 End*** 時(shí),IAsyncResult對(duì)象表示的異步操作還未完成,則 End*** 將在異步操作完成之前阻塞調(diào)用線(xiàn)程。
b) End*** 方法的返回值與其同步副本的返回值類(lèi)型相同。End*** 方法帶有該方法同步版本的簽名中聲明的所有out 和 ref 參數(shù)以及由BeginInvoke返回的IAsyncResult,規(guī)范上 IAsyncResult 參數(shù)放最后。
i. 要想獲得返回結(jié)果,必須調(diào)用的方法;
ii. 若帶有out 和 ref 參數(shù),實(shí)現(xiàn)上委托也要帶有out 和 ref 參數(shù),以便在回調(diào)中獲得對(duì)應(yīng)引用傳參值做相應(yīng)邏輯;
現(xiàn)在我們清楚了IAsyncResult設(shè)計(jì)模式的設(shè)計(jì)規(guī)范,接下來(lái)我們?cè)偻ㄟ^(guò)IAsyncResult異步編程模式的三個(gè)經(jīng)典場(chǎng)合來(lái)加深理解。
#p#
一、基于IAsyncResult構(gòu)造一個(gè)異步API
現(xiàn)在來(lái)構(gòu)建一個(gè)IAsyncResult的類(lèi),并且實(shí)現(xiàn)異步調(diào)用。
- // 帶ref參數(shù)的自定義委托
- public delegate void RefAsyncCallback(ref string resultStr, IAsyncResult ar);
- public class CalculateAsyncResult : IAsyncResult
- {
- private int _calcNum1;
- private int _calcNum2;
- private RefAsyncCallback _userCallback;
- public CalculateAsyncResult(int num1, int num2, RefAsyncCallback userCallback, object asyncState)
- {
- this._calcNum1 = num1;
- this._calcNum2 = num2;
- this._userCallback = userCallback;
- this._asyncState = asyncState;
- // 異步執(zhí)行操作
- ThreadPool.QueueUserWorkItem((obj) => { AsyncCalculate(obj); }, this);
- }
- #region IAsyncResult接口
- private object _asyncState;
- public object AsyncState { get { return _asyncState; } }
- private ManualResetEvent _asyncWaitHandle;
- public WaitHandle AsyncWaitHandle
- {
- get
- {
- if (this._asyncWaitHandle == null)
- {
- ManualResetEvent event2 = new ManualResetEvent(false);
- Interlocked.CompareExchange<ManualResetEvent>(ref this._asyncWaitHandle, event2, null);
- }
- return _asyncWaitHandle;
- }
- }
- private bool _completedSynchronously;
- public bool CompletedSynchronously { get { return _completedSynchronously; } }
- private bool _isCompleted;
- public bool IsCompleted { get { return _isCompleted; } }
- #endregion
- /// <summary>
- ///
- /// 存儲(chǔ)最后結(jié)果值
- /// </summary>
- public int FinnalyResult { get; set; }
- /// <summary>
- /// End方法只應(yīng)調(diào)用一次,超過(guò)一次報(bào)錯(cuò)
- /// </summary>
- public int EndCallCount = 0;
- /// <summary>
- /// ref參數(shù)
- /// </summary>
- public string ResultStr;
- /// <summary>
- /// 異步進(jìn)行耗時(shí)計(jì)算
- /// </summary>
- /// <param name="obj">CalculateAsyncResult實(shí)例本身</param>
- private static void AsyncCalculate(object obj)
- {
- CalculateAsyncResult asyncResult = obj as CalculateAsyncResult;
- Thread.SpinWait(1000);
- asyncResult.FinnalyResult = asyncResult._calcNum1 * asyncResult._calcNum2;
- asyncResult.ResultStr = asyncResult.FinnalyResult.ToString();
- // 是否同步完成
- asyncResult._completedSynchronously = false;
- asyncResult._isCompleted = true;
- ((ManualResetEvent)asyncResult.AsyncWaitHandle).Set();
- if (asyncResult._userCallback != null)
- asyncResult._userCallback(ref asyncResult.ResultStr, asyncResult);
- }
- }
- public class CalculateLib
- {
- public IAsyncResult BeginCalculate(int num1, int num2, RefAsyncCallback userCallback, object asyncState)
- {
- CalculateAsyncResult result = new CalculateAsyncResult(num1, num2, userCallback, asyncState);
- return result;
- }
- public int EndCalculate(ref string resultStr, IAsyncResult ar)
- {
- CalculateAsyncResult result = ar as CalculateAsyncResult;
- if (Interlocked.CompareExchange(ref result.EndCallCount, 1, 0) == 1)
- {
- throw new Exception("End方法只能調(diào)用一次。");
- }
- result.AsyncWaitHandle.WaitOne();
- resultStr = result.ResultStr;
- return result.FinnalyResult;
- }
- public int Calculate(int num1, int num2, ref string resultStr)
- {
- resultStr = (num1 * num2).ToString();
- return num1 * num2;
- }
- }
使用上面通過(guò)IAsyncResult設(shè)計(jì)模式實(shí)現(xiàn)的帶ref引用參數(shù)的異步操作,我將展示三種阻塞式響應(yīng)異步調(diào)用和一種無(wú)阻塞式委托響應(yīng)異步調(diào)用。即:
1. 執(zhí)行異步調(diào)用后,若我們需要控制后續(xù)執(zhí)行代碼在異步操作執(zhí)行完之后執(zhí)行,可通過(guò)下面三種方式阻止其他工作:
a) IAsyncResult的AsyncWaitHandle屬性,待異步操作完成時(shí)獲得信號(hào)。
b) 通過(guò)IAsyncResult的IsCompleted屬性進(jìn)行輪詢(xún)。通過(guò)輪詢(xún)還可實(shí)現(xiàn)進(jìn)度條功能。
c) 調(diào)用異步操作的 End*** 方法。
2. 執(zhí)行異步調(diào)用后,若我們不需要阻止后續(xù)代碼的執(zhí)行,那么我們可以把異步執(zhí)行操作后的響應(yīng)放到回調(diào)中進(jìn)行。
- public class Calculate_Test
- {
- public static void Test()
- {
- CalculateLib cal = new CalculateLib();
- // 基于IAsyncResult構(gòu)造一個(gè)異步API
- IAsyncResult calculateResult = cal.BeginCalculate(123, 456, AfterCallback, cal);
- // 執(zhí)行異步調(diào)用后,若我們需要控制后續(xù)執(zhí)行代碼在異步操作執(zhí)行完之后執(zhí)行,可通過(guò)下面三種方式阻止其他工作:
- // 1、IAsyncResult 的 AsyncWaitHandle 屬性,帶異步操作完成時(shí)獲得信號(hào)。
- // 2、通過(guò) IAsyncResult 的 IsCompleted 屬性進(jìn)行輪詢(xún)。通過(guò)輪詢(xún)還可實(shí)現(xiàn)進(jìn)度條功能。
- // 3、調(diào)用異步操作的 End*** 方法。
- // ***********************************************************
- // 1、calculateResult.AsyncWaitHandle.WaitOne();
- // 2、while (calculateResult.IsCompleted) { Thread.Sleep(1000); }
- // 3、
- string resultStr = string.Empty;
- int result = cal.EndCalculate(ref resultStr, calculateResult);
- }
- /// <summary>
- /// 異步操作完成后做出響應(yīng)
- /// </summary>
- private static void AfterCallback(ref string resultStr, IAsyncResult ar)
- {
- // 執(zhí)行異步調(diào)用后,若我們不需要阻止后續(xù)代碼的執(zhí)行,那么我們可以把異步執(zhí)行操作后的響應(yīng)放到回調(diào)中進(jìn)行。
- CalculateLib cal = ar.AsyncState as CalculateLib;
- //int result = cal.EndInvoke(ref resultStr, calculateResult1);
- //if (result > 0) { }
- }
- }
#p#
二、使用委托進(jìn)行異步編程
對(duì)于委托,編譯器會(huì)為我們生成同步調(diào)用方法“invoke”以及異步調(diào)用方法“BeginInvoke”和“EndInvoke”。對(duì)于異步調(diào)用方式,公共語(yǔ)言運(yùn)行庫(kù) (CLR) 將對(duì)請(qǐng)求進(jìn)行排隊(duì)并立即返回到調(diào)用方,由線(xiàn)程池的線(xiàn)程調(diào)度目標(biāo)方法并與提交請(qǐng)求的原始線(xiàn)程并行運(yùn)行。
異步委托是快速為方法構(gòu)建異步調(diào)用的方式,它基于IAsyncResult設(shè)計(jì)模式實(shí)現(xiàn)的異步調(diào)用,即,通過(guò)BeginInvoke返回IAsyncResult對(duì)象;通過(guò)EndInvoke獲取結(jié)果值。
示例:
上節(jié)的CalculateLib類(lèi)中的同步方法以及所要實(shí)用到的委托如下:
- // 帶ref參數(shù)的自定義委托
- public delegate int AsyncInvokeDel(int num1, int num2, ref string resultStr);
- public int Calculate(int num1, int num2, ref string resultStr)
- {
- resultStr = (num1 * num2).ToString();
- return num1 * num2;
- }
然后,通過(guò)委托進(jìn)行同步或異步調(diào)用:
- AsyncInvokeDel calculateAction = cal.Calculate;
- string resultStrAction = string.Empty;
- // int result1 = calculateAction.Invoke(123, 456, ref resultStrAction);
- IAsyncResult calculateResult1 = calculateAction.BeginInvoke(123, 456, ref resultStrAction, null, null);
- int result1 = calculateAction.EndInvoke(ref resultStrAction, calculateResult1);
#p#
三、多線(xiàn)程操作控件
訪(fǎng)問(wèn) Windows 窗體控件本質(zhì)上不是線(xiàn)程安全的。如果有兩個(gè)或多個(gè)線(xiàn)程操作某一控件的狀態(tài),則可能會(huì)迫使該控件進(jìn)入一種不一致的狀態(tài)。還可能出現(xiàn)其他與線(xiàn)程相關(guān)的 bug,包括爭(zhēng)用情況和死鎖。確保以線(xiàn)程安全方式訪(fǎng)問(wèn)控件非常重要。
不過(guò),在有些情況下,您可能需要多線(xiàn)程調(diào)用控件的方法。.NET Framework 提供了從任何線(xiàn)程操作控件的方式:
1. 非安全方式訪(fǎng)問(wèn)控件(此方式請(qǐng)永遠(yuǎn)不要再使用)
多線(xiàn)程訪(fǎng)問(wèn)窗口中的控件,可以在窗口的構(gòu)造函數(shù)中將Form的CheckForIllegalCrossThreadCalls靜態(tài)屬性設(shè)置為false。
- // 獲取或設(shè)置一個(gè)值,該值指示是否捕獲對(duì)錯(cuò)誤線(xiàn)程的調(diào)用,
- // 這些調(diào)用在調(diào)試應(yīng)用程序時(shí)訪(fǎng)問(wèn)控件的System.Windows.Forms.Control.Handle屬性。
- // 如果捕獲了對(duì)錯(cuò)誤線(xiàn)程的調(diào)用,則為 true;否則為 false。
- public static bool CheckForIllegalCrossThreadCalls { get; set; }
2. 安全方式訪(fǎng)問(wèn)控件
原理:從一個(gè)線(xiàn)程封送調(diào)用并跨線(xiàn)程邊界將其發(fā)送到另一個(gè)線(xiàn)程,并將調(diào)用插入到創(chuàng)建控件線(xiàn)程的消息隊(duì)列中,當(dāng)控件創(chuàng)建線(xiàn)程處理這個(gè)消息時(shí),就會(huì)在自己的上下文中執(zhí)行傳入的方法。(此過(guò)程只有調(diào)用線(xiàn)程和創(chuàng)建控件線(xiàn)程,并沒(méi)有創(chuàng)建新線(xiàn)程)
注意:從一個(gè)線(xiàn)程封送調(diào)用并跨線(xiàn)程邊界將其發(fā)送到另一個(gè)線(xiàn)程會(huì)耗費(fèi)大量的系統(tǒng)資源,所以應(yīng)避免重復(fù)調(diào)用其他線(xiàn)程上的控件。
1) 使用BackgroundWork后臺(tái)輔助線(xiàn)程控件控件(AsyncOperationManager類(lèi)和AsyncOperation類(lèi)幫助器方式)。
2) 結(jié)合TaskScheduler.FromCurrentSynchronizationContext() 和Task 實(shí)現(xiàn)。
3) 使用Control類(lèi)上提供的Invoke 和BeginInvoke方法。
因本文主要解說(shuō)IAsyncResult異步編程模式,所以只詳細(xì)分析Invoke 和BeginInvoke跨線(xiàn)程訪(fǎng)問(wèn)控件方式。
Control類(lèi)實(shí)現(xiàn)了ISynchronizeInvoke接口,提供了Invoke和BeginInvoke方法來(lái)支持其它線(xiàn)程更新GUI界面控件的機(jī)制。
- public interface ISynchronizeInvoke
- {
- // 獲取一個(gè)值,該值指示調(diào)用線(xiàn)程是否與控件的創(chuàng)建線(xiàn)程相同。
- bool InvokeRequired { get; }
- // 在控件創(chuàng)建的線(xiàn)程上異步執(zhí)行指定委托。
- AsyncResult BeginInvoke(Delegate method, params object[] args);
- object EndInvoke(IAsyncResult asyncResult);
- // 在控件創(chuàng)建的線(xiàn)程上同步執(zhí)行指定委托。
- object Invoke(Delegate method, params object[] args);
- }
1) Control類(lèi)的 Invoke,BeginInvoke 內(nèi)部實(shí)現(xiàn)如下:
a) Invoke (同步調(diào)用)先判斷控件創(chuàng)建線(xiàn)程與當(dāng)前線(xiàn)程是否相同,相同則直接調(diào)用委托方法;否則使用Win32API的PostMessage 異步執(zhí)行。
b) BeginInvoke (異步調(diào)用)使用Win32API的PostMessage 異步執(zhí)行.
- UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle)
- , threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
- [DllImport("user32.dll", CharSet=CharSet.Auto)]
- public static extern bool PostMessage(HandleRefhwnd, intmsg, IntPtrwparam, IntPtrlparam);
PostMessage 是windows api,用來(lái)把一個(gè)消息發(fā)送到一個(gè)窗口的消息隊(duì)列。這個(gè)方法是異步的,也就是該方法封送完畢后馬上返回,不會(huì)等待委托方法的執(zhí)行結(jié)束,調(diào)用者線(xiàn)程將不會(huì)被阻塞。(對(duì)應(yīng)同步方法的windows api是:SendMessage())
2) InvokeRequired
獲取一個(gè)值,該值指示調(diào)用線(xiàn)程是否與控件的創(chuàng)建線(xiàn)程相同。內(nèi)部關(guān)鍵如下:
- Int windowThreadProcessId = SafeNativeMethods.GetWindowThreadProcessId(ref2, out num);
- Int currentThreadId = SafeNativeMethods.GetCurrentThreadId();
- return (windowThreadProcessId != currentThreadId);
即返回“通過(guò)GetWindowThreadProcessId功能函數(shù)得到創(chuàng)建指定窗口線(xiàn)程的標(biāo)識(shí)和創(chuàng)建窗口的進(jìn)程的標(biāo)識(shí)符與當(dāng)前線(xiàn)程Id進(jìn)行比較”的結(jié)果。
3) 示例(詳見(jiàn)示例文件)
在使用的時(shí)候,我們使用 this.InvokeRequired 屬性來(lái)判斷是使用Invoke或BeginInvoke 還是直接調(diào)用方法。
- private void InvokeControl(object mainThreadId)
- {
- if (this.InvokeRequired)
- {
- this.Invoke(new Action<String>(ChangeText), "InvokeRequired = true.改變控件Text值");
- //this.textBox1.Invoke(new Action<int>(InvokeCount), (int)mainThreadId);
- }
- else
- {
- ChangeText("在創(chuàng)建控件的線(xiàn)程上,改變控件Text值");
- }
- }
- private void ChangeText(String str)
- {
- this.textBox1.Text += str;
- }
注意,在InvokeControl方法中使用 this.Invoke(Delegate del) 和使用 this.textBox1.Invoke(Delegate del) 效果是一樣的。因?yàn)樵趫?zhí)行Invoke或BeginInvoke時(shí),內(nèi)部首先調(diào)用 FindMarshalingControl() 進(jìn)行一個(gè)循環(huán)向上回溯,從當(dāng)前控件開(kāi)始回溯父控件,直到找到最頂級(jí)的父控件,用它作為封送對(duì)象。也就是說(shuō) this.textBox1.Invoke(Delegate del) 會(huì)追溯到和 this.Invoke(Delegate del) 一樣的起點(diǎn)。(子控件的創(chuàng)建線(xiàn)程一定是創(chuàng)建父控件的線(xiàn)程,所以這種追溯不會(huì)導(dǎo)致將調(diào)用封送到錯(cuò)誤的目的線(xiàn)程)
本節(jié)到此結(jié)束,本節(jié)主要講了異步編程模式之一“異步編程模型(APM)”,是基于IAsyncResult設(shè)計(jì)模式實(shí)現(xiàn)的異步編程方式,并且構(gòu)建了一個(gè)繼承自IAsyncResult接口的示例,及展示了這種模式在委托及跨線(xiàn)程訪(fǎng)問(wèn)控件上的經(jīng)典應(yīng)用。下一節(jié)中,我將為大家介紹基于事件的編程模型……
原文鏈接:http://www.cnblogs.com/heyuquan/archive/2013/03/22/2976420.html