.Net組件程式設計之非同步呼叫

來源:互聯網
上載者:User

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

一 委託的老調重彈

public class Operation    {        public int Addition(int num1, int num2)        {            return num1 + num2;        }        public int Subtraction(int num1, int num2)        {            return num1 - num2;        }    }

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

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

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

public sealed class OperationDelegate : MulticastDelegate    {        public OperationDelegate(Object target, int methodPtr) { }        public virtual Invoke(int num1,int num2)        {            ……        }        public virtual IAsyncResult BeginInvoke(int num1,int num2,AsyncCallback callback,object asyncState)        {            ……        }        public virtual int EndInvoke(IAsyncResult result)        {            ……        }    }

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

二 非同步呼叫編程模型

圖1

在上圖我們所見的有這幾個模組, .NET線程池、非同步呼叫請求隊列和一個應用程式的主線程。
假使現在從任務1開始執行到任務2、任務3,到了任務3的時候,任務3請求.NET執行非同步作業,如圖2

圖2

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

圖3

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

這裡要說的就是,非同步呼叫看起來是並存執行的,實際剛開始的時候還是順序的,不過這時間在實際情況中是忽略不計的, 可以認為就是並存執行的吧。

 三 BeginInvoke()、EndInvoke()

3.1 BeginInvoke()

BeginInvoke()函數定義如下:

1publicvirtual 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的定義:

public interface IAsyncResult    {        object AsyncState { get; }        WaitHandle AsyncWaitHandle { get; }        bool CompletedSynchronously { get; }        bool IsCompleted { get; }    }

對於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():

public class OperationTest
    {

        public void Test()
        {
            Operation operation = new Operation();
            OperationDelegate Additiondelegate = operation.Addition;
            int Result;
            Result = GetResult(Additiondelegate.BeginInvoke(3, 4, null, null));
        }

        private int GetResult(IAsyncResult asyncresult)
        {
            AsyncResult asyncResult = (AsyncResult)asyncresult;
            OperationDelegate operationdelegate = asyncResult.AsyncDelegate as

OperationDelegate;
            if (operationdelegate != null)
            {
                Debug.Assert(asyncResult.EndInvokeCalled == false);//EndInvoke()是否被調用過
                return operationdelegate.EndInvoke(asyncResult);
            }
            return -1;
        }
    }

3.4 輪循或等待

看到這裡,善于思考的朋友會發現,還存在著一個很大的問題,就是發起非同步呼叫的用戶端,如何知道自己 的非同步函數是否執行完畢了?或者是想等待一會,做一些其他的處理,然後再繼續等待,該怎麼來實現呢?

從BeginInvoke()返回的IAsyncResult介面有個AsyncWaitHandle屬性,它是幹嗎的呢?就把它理解為訊息接收器吧。

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

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

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

Operation operation = new Operation();
OperationDelegate Additiondelegate = operation.Addition;
IAsyncResult asyncResult = Additiondelegate.BeginInvoke(2, 3, null, null);
while (asyncResult.IsCompleted == false)//判斷非同步任務是否完成
{
     asyncResult.AsyncWaitHandle.WaitOne(10,false);//如果任務完成則不會阻塞 否則阻塞當前線程10毫秒
    //這裡做一些其他動作
}
int Result;
Result = Additiondelegate.EndInvoke(asyncResult);
Debug.Assert(Result == 5);

3.5 使用回呼函數

現在我們要來說說BeginInvoke()的第三個參數了, public delegate void AsyncCallback(IAsyncResult ar);

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

public class OperationTest
    {

        public void Test()
        {
            Operation operation = new Operation();
            OperationDelegate Additiondelegate = operation.Addition;

            Additiondelegate.BeginInvoke(2, 3, new AsyncCallback(OnCallBack), null);
        }

        private void OnCallBack(IAsyncResult asyncresult)
        {
            AsyncResult asyncResult = (AsyncResult)asyncresult;
            OperationDelegate operationdelegate = asyncResult.AsyncDelegate as

OperationDelegate;
            if (operationdelegate != null)
            {
                Debug.Assert(asyncResult.EndInvokeCalled == false);
                int result=operationdelegate.EndInvoke(asyncResult);
                Console.WriteLine("Operation returned" + result.ToString());
            }
        }
    }

這裡需要說的是在非同步任務完成時,執行的回呼函數依然是在子線程當中,並不是在主線程中執行回呼函數的。

題外話:最常見的就是在Winform開發中,Form中發起非同步呼叫,然後回呼函數操作Form中的控制項或者是

值的時候就會報錯, 就是這個原因,因為它們不在一個線程也不在一個上下文中,基於.NET安全性原則這種操作是不允許的。



相關文章

Beyond APAC's No.1 Cloud

19.6% IaaS Market Share in Asia Pacific - Gartner IT Service report, 2018

Learn more >

Apsara Conference 2019

The Rise of Data Intelligence, September 25th - 27th, Hangzhou, China

Learn more >

Alibaba Cloud Free Trial

Learn and experience the power of Alibaba Cloud with a free trial worth $300-1200 USD

Learn more >

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。