.Net元件程式設計之異步調用
說到異步調用,在腦海中首先想到就是BeginInvoke(),在一些常用對象中我們也會常常見到Invoke()和BeginInvoke(), 要想讓自己的元件可以被用戶端調用或者是異步調用,這樣的設計是合理的,這也是元件異步機制當中的一條 (說句題外話--其實大多數知識都隐藏在我們平時經常見到的對象或者是代碼裡,隻不過是沒有去細心的發現) 在.NET中首先就會想到使用委托來進行異步調用,關于委托的定義在 委托與事件一文中已經大概的說過了,文中隻是對委托進行了 大概的講解,并沒有對委托的使用來說明或者是例舉一些示例。 在本篇中将會對委托進行一個基礎的揭底,主要方向是異步調用。
一 委托的老調重彈
1
2
3
4
5
6
7
8
9
10
11
<code> </code><code>1 </code><code>public</code> <code>class</code> <code>Operation </code>
<code> </code><code>2 { </code>
<code> </code><code>3 </code><code>public</code> <code>int</code> <code>Addition(</code><code>int</code> <code>num1, </code><code>int</code> <code>num2) </code>
<code> </code><code>4 { </code>
<code> </code><code>5 </code><code>return</code> <code>num1 + num2; </code>
<code> </code><code>6 } </code>
<code> </code><code>7 </code><code>public</code> <code>int</code> <code>Subtraction(</code><code>int</code> <code>num1, </code><code>int</code> <code>num2) </code>
<code> </code><code>8 { </code>
<code> </code><code>9 </code><code>return</code> <code>num1 - num2;</code>
<code> </code><code>10 }</code>
<code> </code><code>11 }</code>
沒有必要直接使用Operation對象來進行加減運算,可以使用委托:
<code>1 </code><code>public</code> <code>delegate</code> <code>int</code> <code>OperationDelegate(</code><code>int</code> <code>num1, </code><code>int</code> <code>num2);</code>
<code>2 </code>
<code>3 Operation operation = </code><code>new</code> <code>Operation();</code>
<code>4 OperationDelegate Additiondelegate = operation.Addition;</code>
<code>5 </code>
<code>6 </code><code>int</code> <code>result;</code>
<code>7 result = Additiondelegate.Invoke(3, 4);</code>
<code>8 Debug.Assert(result == 7);</code>
在使用委托進行調用的時候,目前線程是被阻塞的,隻有當委托執行完畢了,才會把控制權交回到目前線程。
不過呢,委托可以用于進行異步調用目标方法的,委托隻是一種特定的類型,編譯器會把我們定義的各式各樣的委托編譯成
對應的類,好比OperationDelegate委托一樣,實則是被編譯成這樣的
12
13
14
15
16
17
18
19
20
<code> </code><code>1 </code><code>public</code> <code>sealed</code> <code>class</code> <code>OperationDelegate : MulticastDelegate</code>
<code> </code><code>2 {</code>
<code> </code><code>3 </code><code>public</code> <code>OperationDelegate(Object target, </code><code>int</code> <code>methodPtr) { }</code>
<code> </code><code>4 </code><code>public</code> <code>virtual</code> <code>Invoke(</code><code>int</code> <code>num1,</code><code>int</code> <code>num2)</code>
<code> </code><code>5 {</code>
<code> </code><code>6 ……</code>
<code> </code><code>7 }</code>
<code> </code><code>8 </code>
<code> </code><code>9 </code><code>public</code> <code>virtual</code> <code>IAsyncResult BeginInvoke(</code><code>int</code> <code>num1,</code><code>int</code> <code>num2,AsyncCallback </code>
<code>10 </code>
<code>11 callback,</code><code>object</code> <code>asyncState)</code>
<code>12 {</code>
<code>13 ……</code>
<code>14 }</code>
<code>15 </code>
<code>16 </code><code>public</code> <code>virtual</code> <code>int</code> <code>EndInvoke(IAsyncResult result)</code>
<code>17 {</code>
<code>18 ……</code>
<code>19 }</code>
<code>20 }</code>
這裡隻是回顧一下委托的定義。
二 異步調用程式設計模型
圖1
在上圖我們所見的有這幾個子產品, .NET線程池、異步調用請求隊列和一個應用程式的主線程。
假使現在從任務1開始執行到任務2、任務3,到了任務3的時候,任務3請求.NET執行異步操作,如圖2
圖2
這個時候【任務3】已經被送入到了【異步請求隊列】中,并且主線程是阻塞狀态的,再看圖3的執行過程:
圖3
線程池會及時的發現【異步請求隊列】中的任務,并且根據任務的資訊,線程池會配置設定一個線程到任務所在的主線程中執行所請求的任務。 在異步任務執行時,這個時候主線程才會從阻塞中撤銷,進入執行狀态,上圖中,就是開始執行任務4。
這裡要說的就是,異步調用看起來是并行執行的,實際剛開始的時候還是順序的,不過這時間在實際情況中是忽略不計的, 可以認為就是并行執行的吧。
三 BeginInvoke()、EndInvoke()
3.1 BeginInvoke()
BeginInvoke()函數定義如下:
<code>1 </code><code>public</code> <code>virtual</code> <code>IAsyncResult BeginInvoke(</code><code>int</code> <code>num1,</code><code>int</code> <code>num2,AsyncCallback callback,</code><code>object</code> <code>asyncState)</code>
<code>2 {</code>
<code>3 ……</code>
<code>4 }</code>
接受OperationDelegate委托定義的原始簽名的輸入參數,還有兩個額外參數,AsyncCallback是系統定義的委托, 用于異步調用完成時回調所用,這裡不做講解,後面會有講到,還有一個是參數是一個狀态對象,也可以認為是容器對象, 也會在後面的章節中講到。
<code>1 Operation operation = </code><code>new</code> <code>Operation();</code>
<code>2 OperationDelegate Additiondelegate = operation.Addition;</code>
<code>3 Additiondelegate.BeginInvoke(3, 4, </code><code>null</code><code>, </code><code>null</code><code>);</code>
3.2 IAsyncResult接口
正如上面所看到的,BeginInvoke函數傳回一個IAsyncResult類型的值,那就來看一下IAsyncResult的定義:
<code>1 </code><code>public</code> <code>interface</code> <code>IAsyncResult</code>
<code>2 {</code>
<code>3 </code><code>object</code> <code>AsyncState { </code><code>get</code><code>; }</code>
<code>4 WaitHandle AsyncWaitHandle { </code><code>get</code><code>; }</code>
<code>5 </code><code>bool</code> <code>CompletedSynchronously { </code><code>get</code><code>; }</code>
<code>6 </code><code>bool</code> <code>IsCompleted { </code><code>get</code><code>; }</code>
<code>7 }</code>
對于IAsyncResult的詳細用法 稍後會有講解
看到第一節的Invoke函數執行後,可以直接擷取到傳回值,怎麼這個BeginInvoke函數執行了傳回
IAsyncResult類型,傳回值在哪呢? 可以通過從BeginInvoke函數獲得的IAsyncResult交給EndInvoke函數來擷取傳回值。
<code>3 </code>
<code>4 IAsyncResult asyncResult = Additiondelegate.BeginInvoke(3, 4, </code><code>null</code><code>, </code><code>null</code><code>);</code>
<code>5 </code><code>int</code> <code>result = Additiondelegate.EndInvoke(asyncResult);</code>
<code>6 Debug.Assert(result == 7);</code>
這裡要說幾點
第一.調用EndInvoke函數的時候,目前線程是被阻塞的,它在等待BeginInvoke函數執行完畢。
第二.雖然委托可以管理多個目标方法,但是在異步調用中,所執行異步調用的委托,内部的管理清單隻能有一個目标方法,不然會報 有異常。
第三.EndInvoke()在每次異步調用操作時 隻能調用一次。
第四.BeginInvoke()傳回的IAsyncResult類型的執行個體,隻能傳入它所調用BeginInvoke()委托的EndInvoke()中,不然也會報有異常。
3.3 AsyncResult
假使一個用戶端在一個代碼段或者是函數中使用BeginInvoke(),而在另一段或者是其他的函數中調用EndInvoke(),這樣用戶端是不是就要儲存IAsyncResult對象,又或者一個用戶端發起異步調用,并且由另一個 用戶端來調用EndInvoke(),這不僅僅要儲存IAsyncResult對象,還需要儲存該委托對象,而且你還得傳送過去。 還好.NET是那麼的機智,有System.Runtime.Remoting.Messaging.AsyncResult類型的存在。
21
22
23
24
25
<code> </code><code>public</code> <code>class</code> <code>AsyncResult : IAsyncResult, IMessageSink</code>
<code> </code><code>{</code>
<code> </code><code>#region IAsyncResult 成員</code>
<code> </code><code>public</code> <code>object</code> <code>AsyncState</code>
<code> </code><code>{</code>
<code> </code><code>get</code> <code>{ </code><code>throw</code> <code>new</code> <code>NotImplementedException(); }</code>
<code> </code><code>}</code>
<code> </code><code>public</code> <code>System.Threading.WaitHandle AsyncWaitHandle</code>
<code> </code><code>public</code> <code>bool</code> <code>CompletedSynchronously</code>
<code> </code><code>public</code> <code>bool</code> <code>IsCompleted</code>
<code> </code><code>#endregion</code>
<code> </code><code>public</code> <code>bool</code> <code>EndInvokeCalled { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>public</code> <code>virtual</code> <code>object</code> <code>AsyncDelegate { </code><code>get</code><code>; }</code>
<code> </code><code>//IMessageSink 成員</code>
<code> </code><code>}</code>
看着上面有個AsyncDelegate的屬性,會不會覺得很漂亮,不錯,它就是原始發起委托的引用,看下如何使用AsyncDelegate來使用EndInvoke():
<code> </code><code>1 </code><code>public</code> <code>class</code> <code>OperationTest</code>
<code> </code><code>3 </code>
<code> </code><code>4 </code><code>public</code> <code>void</code> <code>Test()</code>
<code> </code><code>6 Operation operation = </code><code>new</code> <code>Operation();</code>
<code> </code><code>7 OperationDelegate Additiondelegate = operation.Addition;</code>
<code> </code><code>8 </code><code>int</code> <code>Result;</code>
<code> </code><code>9 Result = GetResult(Additiondelegate.BeginInvoke(3, 4, </code><code>null</code><code>, </code><code>null</code><code>));</code>
<code>10 }</code>
<code>11 </code>
<code>12 </code><code>private</code> <code>int</code> <code>GetResult(IAsyncResult asyncresult)</code>
<code>13 {</code>
<code>14 AsyncResult asyncResult = (AsyncResult)asyncresult;</code>
<code>15 OperationDelegate operationdelegate = asyncResult.AsyncDelegate </code><code>as</code>
<code>16 </code>
<code>17 OperationDelegate;</code>
<code>18 </code><code>if</code> <code>(operationdelegate != </code><code>null</code><code>)</code>
<code>19 {</code>
<code>20 Debug.Assert(asyncResult.EndInvokeCalled == </code><code>false</code><code>);</code><code>//EndInvoke()是否被調用過</code>
<code>21 </code><code>return</code> <code>operationdelegate.EndInvoke(asyncResult);</code>
<code>22 }</code>
<code>23 </code><code>return</code> <code>-1;</code>
<code>24 }</code>
<code>25 }</code>
3.4 輪循或等待
看到這裡,善于思考的朋友會發現,還存在着一個很大的問題,就是發起異步調用的用戶端,如何知道自己 的異步函數是否執行完畢了?或者是想等待一會,做一些其他的處理,然後再繼續等待,該怎麼來實作呢?
從BeginInvoke()傳回的IAsyncResult接口有個AsyncWaitHandle屬性,它是幹嗎的呢?就把它了解為消息接收器吧。
<code>3 IAsyncResult asyncResult = Additiondelegate.BeginInvoke(2, 3, </code><code>null</code><code>, </code><code>null</code><code>);</code>
<code>4 asyncResult.AsyncWaitHandle.WaitOne();</code><code>//如果任務完成則不會阻塞 否則阻塞目前線程</code>
<code>5 </code><code>int</code> <code>Result;</code>
<code>6 Result = Additiondelegate.EndInvoke(asyncResult);</code>
<code>7 Debug.Assert(Result == 5);</code>
代碼和3.2的幾乎相同,差別就是這段代碼保證了EndInvoke()的調用者不會被阻塞。
看一下等待一下,如果沒完成處理其他任務,回來再等待是怎麼實作的。
<code> </code><code>1 Operation operation = </code><code>new</code> <code>Operation();</code>
<code> </code><code>2 OperationDelegate Additiondelegate = operation.Addition;</code>
<code> </code><code>3 IAsyncResult asyncResult = Additiondelegate.BeginInvoke(2, 3, </code><code>null</code><code>, </code><code>null</code><code>);</code>
<code> </code><code>4 </code><code>while</code> <code>(asyncResult.IsCompleted == </code><code>false</code><code>)</code><code>//判斷異步任務是否完成</code>
<code> </code><code>5 {</code>
<code> </code><code>6 asyncResult.AsyncWaitHandle.WaitOne(10,</code><code>false</code><code>);</code><code>//如果任務完成則不會阻塞 否則阻塞目前線程10毫秒</code>
<code> </code><code>7 </code><code>//這裡做一些其他操作</code>
<code> </code><code>8 }</code>
<code> </code><code>9 </code><code>int</code> <code>Result;</code>
<code>10 Result = Additiondelegate.EndInvoke(asyncResult);</code>
<code>11 Debug.Assert(Result == 5);</code>
3.5 使用回調函數
現在我們要來說說BeginInvoke()的第三個參數了, public delegate void AsyncCallback(IAsyncResult ar);
第三個參數就是系統提供的一個委托類型,委托簽名也都看到了。 使用回調函數的好處就是不需要去處理等待操作了,因為在異步任務完成的時候, 會調用你傳給BeginInvoke()裡AsyncCallback委托所關聯的目标方法。
<code> </code><code>9 Additiondelegate.BeginInvoke(2, 3, </code><code>new</code> <code>AsyncCallback(OnCallBack), </code><code>null</code><code>);</code>
<code>12 </code><code>private</code> <code>void</code> <code>OnCallBack(IAsyncResult asyncresult)</code>
<code>20 Debug.Assert(asyncResult.EndInvokeCalled == </code><code>false</code><code>);</code>
<code>21 </code><code>int</code> <code>result=operationdelegate.EndInvoke(asyncResult);</code>
<code>22 Console.WriteLine(</code><code>"Operation returned"</code> <code>+ result.ToString());</code>
<code>23 }</code>
這裡需要說的是在異步任務完成時,執行的回調函數依然是在子線程當中,并不是在主線程中執行回調函數的。
題外話:最常見的就是在Winform開發中,Form中發起異步調用,然後回調函數操作Form中的控件或者是
值的時候就會報錯, 就是這個原因,因為它們不在一個線程也不在一個上下文中,基于.NET安全政策這種操作是不允許的。
END
本文轉自jinyuan0829 51CTO部落格,原文連結:http://blog.51cto.com/jinyuan/1421925,如需轉載請自行聯系原作者