引言
在進行一個對象的方法調用時,對象執行調用期間用戶端通常是被阻塞的,且只有該方法執行完畢並且返回時,控制權才會回到用戶端。當提及這樣的需求,作為開發人員的你會使用何種方式完成呢?通常情況下我們都會使用多線程來滿足這種需求。然而對於個人而言,實現一個強健的非同步呼叫執行是不堪重負的,需要嘔心瀝血的設計、實現、測試。不過,在使用.Net開發時,我們可以使用非同步呼叫方式來滿足客戶需求。
什麼是非同步呼叫
.Net設計師考慮到了這個問題,為了讓你集中精力在手頭的業務問題的解決,而非繁雜的非同步呼叫問題,提出了非同步呼叫。有的時候你需要把調用的方法A放在後台執行,此時控制權可以立即返回用戶端,執行完方法A後以某種方式通知用戶端A方法已經執行完畢,這種執行模式被稱為非同步方法呼叫調。實現這種操作就需要使用非同步呼叫。在學習非同步呼叫之前,首先我們要瞭解委託的內部原理。
使用非同步呼叫離不開委託
我們建立一個Windows Form應用程式。
項目中添加一個計算類Calculator,包含加、減法運算
public class Calculator{ public int Add(int arg1, int arg2) { return arg1 + arg2; } public int Subtract(int arg1, int arg2) { return arg1 - arg2; }}
在Form1中添加一個按鈕
在Form1類中添加一個委託
public delegate int BinaryOperation(int arg1, int arg2);
雙擊按鈕添加Click事件
private void button1_Click(object sender, EventArgs e){ Calculator c = new Calculator(); // 也可以使用+=,後面有解釋,與這裡知識無關 BinaryOperation oppDel = c.Add; int result = 0; result = oppDel(2, 3); MessageBox.Show("result = " + result);}
運行後顯示結果:result=5
委託的原理
運行到此,聰明的人會在思考一個問題,這種委託方式內部是如何?的。
編譯器發現BinaryOperation委託,他會將BinaryOperation委託代碼編譯為
public sealed class BinaryOperation : MulticastDelegate{public BinaryOperation(object target, int methodPtr){}public virtual int Invoke(int arg1, int arg2){}public virtual IAsyncResult Begin(int arg1, int arg2, AsyncCallback callback, object asyncState){}public virtual int EndInvoke(IAsyncResult result){}}
當我們調用委託oppDel(2, 3)時,編譯器會轉變對Invoke(int arg1, int arg2)的調用。Invoke方法會阻塞調用者,在調用者線程上執行這個方法,然後把控制權交給調用者。
BinaryOperation oppDel = c.Add;int result = 0;result = oppDel(2, 3);
BinaryOperation類是從MulticastDelegate類派生而來的,MulticastDelegate提供了每個委託都有的內部委託列表實現,而這些委託均派生自抽象類別Delegate(本部落格事件章節有詳細介紹)。編譯器也聲明了兩個方法來管理非同步方法呼叫調用。BeginInvoke()和EndInvoke(),而他們的正確使用是本章節的主題。
非同步呼叫的編程模型
要支援非同步呼叫,多線程是必不可少的。如果.Net為每個非同步呼叫方法都建立一個新線程,無疑是一個巨大的資源浪費,同時也會導致效能的損失。一個比較好的方法是使用已經建立的背景工作執行緒池。.Net中正有這樣的線程池,被稱為.Net線程池。在.Net中隱蔽了非同步呼叫的互動。這裡我們主要闡述阻塞、等待、輪循、完成回調。
總的思路是:BeginInvoke()發起一個非同步方法呼叫調用,在將請求放入隊列時,會短暫的阻塞用戶端,然後控制權又回到用戶端。EndInvoke()管理方法的完成,包括輸出參數和傳回值,以及錯誤處理。
使用BeginInvoke()和EndInvoke()
編譯器產生的BeginInvoke()和EndInvoke()有如下格式:
public virtual IAsyncResult BeginInvoke(<input and input/output parameters>, AsyncCallback callback, object asyncState);public virtual <return value> EndInvoke(<outpub and input/output parameters>, IAsyncResult asyncResult);
BeginInvoke接受兩個額外參數:AsyncCallback回調和asyncState對象,這兩個不在原始委託簽名中。回調參數實際上是一個委派物件,代表對一個回調方法的引用,該方法用於接收方法完成事件的通知。asyncState是一個通用對象,該對象傳遞任何狀態資訊,這些資訊是處理方法完成的一方所必須的。這兩個參數是可選的,可以傳遞兩個null或選擇其一。
Calculator c = new Calculator();BinaryOperation oppDel = c.Add;oppDel.BeginInvoke(2, 3, null, null);
通過這樣的方式,任意一個類都支援同步或非同步呼叫。
如何獲得非同步方法呼叫調用結果
從上面的代碼不難看出BeginInvoke方法返回一個實現了IAsyncResult介面對象,IAsyncResult定義如下:
public interface IAsyncResult{ object AsyncState{get;} WaitHandle AsyncWaitHandle{get;} bool CompletedSynchronously{get;} bool IsCompleted{get;}}
我們可以看到,在.Net中使用IAsynResult對象唯一標示BeginInvoke方法的傳回值,你可以把IAsyncResult對象傳遞給EndInvoke(),以此標識需要取得那個非同步方法呼叫的執行結果。
介面添加一個《非同步計算1》按鈕,
Click事件
private void button2_Click(object sender, EventArgs e){ Calculator c = new Calculator(); BinaryOperation oppDel = c.Add; IAsyncResult asyncResult1 = oppDel.BeginInvoke(2, 3, null, null); IAsyncResult asyncResult2 = oppDel.BeginInvoke(5, 5, null, null); // 注意輸出順序 MessageBox.Show(oppDel.EndInvoke(asyncResult2).ToString()); MessageBox.Show(oppDel.EndInvoke(asyncResult1).ToString());}
輸出結果10,5
上面案例展示幾個關鍵點:
1、EndInvoke主要用途是擷取輸出參數的和方法的傳回值,EndInvoke阻塞了調用者直到他等待的方法傳回值。
2、同一個委派物件會有多個非同步呼叫,如oppDel委派物件,我們進行了兩次BeginInvoke。我們通過傳入EndInvoke的IAsyncResult對象標識對應關係。
3、非同步呼叫順序是不可測的,所以不要對次序做任何假設。
非同步呼叫的優點
非同步呼叫可以提高應用程式的可用性
提高其輸送量和效能
提供更好的向外擴充能力
持續更新中...