.Net組件程序設計之異步調用

 .Net組件程序設計之異步調用編程

說到異步調用,在腦海中首先想到就是BeginInvoke(),在一些經常使用對象中咱們也會經常見到Invoke()和BeginInvoke(), 要想讓本身的組件能夠被客戶端調用或者是異步調用,這樣的設計是合理的,這也是組件異步機制當中的一條 (說句題外話--其實大多數知識都隱藏在咱們平時常常見到的對象或者是代碼裏,只不過是沒有去細心的發現) 在.NET中首先就會想到使用委託來進行異步調用,關於委託的定義在 委託與事件一文中已經大概的說過了,文中只是對委託進行了 大概的講解,並無對委託的使用來講明或者是例舉一些示例。 在本篇中將會對委託進行一個基礎的揭底,主要方向是異步調用。安全

一 委託的老調重彈異步

 1     public class Operation
 2     {
 3         public int Addition(int num1, int num2)
 4         {
 5             return num1 + num2;
 6         }
 7         public int Subtraction(int num1, int num2)
 8         {
 9             return num1 - num2;
10         }
11     }

沒有必要直接使用Operation對象來進行加減運算,可使用委託:async

1 public delegate int OperationDelegate(int num1, int num2);
2 
3 Operation operation = new Operation();
4 OperationDelegate Additiondelegate = operation.Addition;
5 
6 int result;
7 result = Additiondelegate.Invoke(3, 4);
8 Debug.Assert(result == 7);

在使用委託進行調用的時候,當前線程是被阻塞的,只有當委託執行完畢了,纔會把控制權交回到當前線程。
不過呢,委託能夠用於進行異步調用目標方法的,委託只是一種特定的類型,編譯器會把咱們定義的各式各樣的委託編譯成
對應的類,比如OperationDelegate委託同樣,實則是被編譯成這樣的函數

 1     public sealed class OperationDelegate : MulticastDelegate
 2     {
 3         public OperationDelegate(Object target, int methodPtr) { }
 4         public virtual Invoke(int num1,int num2)
 5         {
 6             ……
 7         }
 8 
 9         public virtual IAsyncResult BeginInvoke(int num1,int num2,AsyncCallback 
10 
11 callback,object asyncState)
12         {
13             ……
14         }
15 
16         public virtual int EndInvoke(IAsyncResult result)
17         {
18             ……
19         }
20     }

這裏只是回顧一下委託的定義。spa

二 異步調用編程模型線程

圖1設計

在上圖咱們所見的有這幾個模塊, .NET線程池、異步調用請求隊列和一個應用程序的主線程。
假使如今從任務1開始執行到任務二、任務3,到了任務3的時候,任務3請求.NET執行異步操做,如圖2code

圖2orm

這個時候【任務3】已經被送入到了【異步請求隊列】中,而且主線程是阻塞狀態的,再看圖3的執行過程:

圖3

線程池會及時的發現【異步請求隊列】中的任務,而且根據任務的信息,線程池會分配一個線程到任務所在的主線程中執行所請求的任務。 在異步任務執行時,這個時候主線程纔會從阻塞中撤銷,進入執行狀態,上圖中,就是開始執行任務4。

這裏要說的就是,異步調用看起來是並行執行的,實際剛開始的時候仍是順序的,不過這時間在實際狀況中是忽略不計的, 能夠認爲就是並行執行的吧。

 三 BeginInvoke()、EndInvoke()

3.1 BeginInvoke()

BeginInvoke()函數定義以下:

1 public virtual IAsyncResult BeginInvoke(int num1,int num2,AsyncCallback callback,object asyncState)
2 {
3    ……
4 }

接受OperationDelegate委託定義的原始簽名的輸入參數,還有兩個額外參數,AsyncCallback是系統定義的委託, 用於異步調用完成時回調所用,這裏不作講解,後面會有講到,還有一個是參數是一個狀態對象,也能夠認爲是容器對象, 也會在後面的章節中講到。

1 Operation operation = new Operation();
2 OperationDelegate Additiondelegate = operation.Addition;
3 Additiondelegate.BeginInvoke(3, 4, null, null);

3.2 IAsyncResult接口

正如上面所看到的,BeginInvoke函數返回一個IAsyncResult類型的值,那就來看一下IAsyncResult的定義:

1     public interface IAsyncResult
2     {
3         object AsyncState { get; }
4         WaitHandle AsyncWaitHandle { get; }
5         bool CompletedSynchronously { get; }
6         bool IsCompleted { get; }
7     }

對於IAsyncResult的詳細用法 稍後會有講解

看到第一節的Invoke函數執行後,能夠直接獲取到返回值,怎麼這個BeginInvoke函數執行了返回

IAsyncResult類型,返回值在哪呢? 能夠經過從BeginInvoke函數得到的IAsyncResult交給EndInvoke函數來獲取返回值。

1 Operation operation = new Operation();
2 OperationDelegate Additiondelegate = operation.Addition;
3 
4 IAsyncResult asyncResult = Additiondelegate.BeginInvoke(3, 4, null, null);
5 int result = Additiondelegate.EndInvoke(asyncResult);
6 Debug.Assert(result == 7);

這裏要說幾點

第一.調用EndInvoke函數的時候,當前線程是被阻塞的,它在等待BeginInvoke函數執行完畢。

第二.雖然委託能夠管理多個目標方法,可是在異步調用中,所執行異步調用的委託,內部的管理列表只能有一個目標方法,否則會報 有異常。

第三.EndInvoke()在每次異步調用操做時 只能調用一次。

第四.BeginInvoke()返回的IAsyncResult類型的實例,只能傳入它所調用BeginInvoke()委託的EndInvoke()中,否則也會報有異常。

 

3.3 AsyncResult

假使一個客戶端在一個代碼段或者是函數中使用BeginInvoke(),而在另外一段或者是其餘的函數中調用EndInvoke(),這樣客戶端是否是就要保存IAsyncResult對象,又或者一個客戶端發起異步調用,而且由另外一個 客戶端來調用EndInvoke(),這不只僅要保存IAsyncResult對象,還須要保存該委託對象,並且你還得傳送過去。 還好.NET是那麼的機智,有System.Runtime.Remoting.Messaging.AsyncResult類型的存在。

    public class AsyncResult : IAsyncResult, IMessageSink
    {

        #region IAsyncResult 成員

        public object AsyncState
        {
            get { throw new NotImplementedException(); }
        }

        public System.Threading.WaitHandle AsyncWaitHandle
        {
            get { throw new NotImplementedException(); }
        }

        public bool CompletedSynchronously
        {
            get { throw new NotImplementedException(); }
        }

        public bool IsCompleted
        {
            get { throw new NotImplementedException(); }
        }

        #endregion

        public bool EndInvokeCalled { get; set; }

        public virtual object AsyncDelegate { get; }


        //IMessageSink 成員
    }

看着上面有個AsyncDelegate的屬性,會不會以爲很漂亮,不錯,它就是原始發起委託的引用,看下如何使用AsyncDelegate來使用EndInvoke():

 1     public class OperationTest
 2     {
 3 
 4         public void Test()
 5         {
 6             Operation operation = new Operation();
 7             OperationDelegate Additiondelegate = operation.Addition;
 8             int Result;
 9             Result = GetResult(Additiondelegate.BeginInvoke(3, 4, null, null));
10         }
11 
12         private int GetResult(IAsyncResult asyncresult)
13         {
14             AsyncResult asyncResult = (AsyncResult)asyncresult;
15             OperationDelegate operationdelegate = asyncResult.AsyncDelegate as 
16 
17 OperationDelegate;
18             if (operationdelegate != null)
19             {
20                 Debug.Assert(asyncResult.EndInvokeCalled == false);//EndInvoke()是否被調用過
21                 return operationdelegate.EndInvoke(asyncResult);
22             }
23             return -1;
24         }
25     }

3.4 輪循或等待

看到這裏,善於思考的朋友會發現,還存在着一個很大的問題,就是發起異步調用的客戶端,如何知道本身 的異步函數是否執行完畢了?或者是想等待一會,作一些其餘的處理,而後再繼續等待,該怎麼來實現呢?

從BeginInvoke()返回的IAsyncResult接口有個AsyncWaitHandle屬性,它是幹嘛的呢?就把它理解爲消息接收器吧。

1 Operation operation = new Operation();
2 OperationDelegate Additiondelegate = operation.Addition;
3 IAsyncResult asyncResult = Additiondelegate.BeginInvoke(2, 3, null, null);
4 asyncResult.AsyncWaitHandle.WaitOne();//若是任務完成則不會阻塞 不然阻塞當前線程
5 int Result;
6 Result = Additiondelegate.EndInvoke(asyncResult);
7 Debug.Assert(Result == 5);

代碼和3.2的幾乎相同,區別就是這段代碼保證了EndInvoke()的調用者不會被阻塞。

看一下等待一下,若是沒完成處理其餘任務,回來再等待是怎麼實現的。

 1 Operation operation = new Operation();
 2 OperationDelegate Additiondelegate = operation.Addition;
 3 IAsyncResult asyncResult = Additiondelegate.BeginInvoke(2, 3, null, null);
 4 while (asyncResult.IsCompleted == false)//判斷異步任務是否完成
 5 {
 6      asyncResult.AsyncWaitHandle.WaitOne(10,false);//若是任務完成則不會阻塞 不然阻塞當前線程10毫秒
 7     //這裏作一些其餘操做
 8 }
 9 int Result;
10 Result = Additiondelegate.EndInvoke(asyncResult);
11 Debug.Assert(Result == 5);

3.5 使用回調函數

如今咱們要來講說BeginInvoke()的第三個參數了, public delegate void AsyncCallback(IAsyncResult ar);

第三個參數就是系統提供的一個委託類型,委託簽名也都看到了。 使用回調函數的好處就是不須要去處理等待操做了,由於在異步任務完成的時候, 會調用你傳給BeginInvoke()裏AsyncCallback委託所關聯的目標方法。

 1     public class OperationTest
 2     {
 3 
 4         public void Test()
 5         {
 6             Operation operation = new Operation();
 7             OperationDelegate Additiondelegate = operation.Addition;
 8 
 9             Additiondelegate.BeginInvoke(2, 3, new AsyncCallback(OnCallBack), null);
10         }
11 
12         private void OnCallBack(IAsyncResult asyncresult)
13         {
14             AsyncResult asyncResult = (AsyncResult)asyncresult;
15             OperationDelegate operationdelegate = asyncResult.AsyncDelegate as 
16 
17 OperationDelegate;
18             if (operationdelegate != null)
19             {
20                 Debug.Assert(asyncResult.EndInvokeCalled == false);
21                 int result=operationdelegate.EndInvoke(asyncResult);
22                 Console.WriteLine("Operation returned" + result.ToString());
23             }
24         }
25     }

這裏須要說的是在異步任務完成時,執行的回調函數依然是在子線程當中,並非在主線程中執行回調函數的。

題外話:最多見的就是在Winform開發中,Form中發起異步調用,而後回調函數操做Form中的控件或者是

值的時候就會報錯, 就是這個緣由,由於它們不在一個線程也不在一個上下文中,基於.NET安全策略這種操做是不容許的。

END

 

 

做者:金源

出處:http://www.cnblogs.com/jin-yuan/

本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面

相關文章
相關標籤/搜索