首頁 > 軟體

C#執行緒委託BeginInvoke與EndInvoke的用法

2022-07-12 22:00:28

我們已經知道 C#當中 存在async/await 、BackGroudWorker類以及TPL(任務並行庫)。當然C#還有一些舊的模式來支援非同步程式設計。

1. BeginInovke和EndInvoke簡單介紹

delegate long MyDel(int first, int second);

class Program
{
    static long Sum(int x, int y)
    {
        Console.WriteLine("------Inside Sum@{0}", DateTime.Now.ToString());
        Thread.Sleep(2000);
        return x + y;
    }

    static void Main(string[] args)
    {
        MyDel del = new MyDel(Sum);

        Console.WriteLine("Before BeginInvoke---@{0}", DateTime.Now.ToString());
        IAsyncResult iar = del.BeginInvoke(3, 5, null, null);
        Console.WriteLine("After BeginInvoke@{0}", DateTime.Now.ToString());

        Console.WriteLine("Doing stuff@{0}", DateTime.Now.ToString());

        long result = del.EndInvoke(iar);
        Console.WriteLine("End Invoke@{0}", DateTime.Now.ToString());

        Console.WriteLine("After EndInvoke:  {0}", result);

        Console.ReadKey();

    }
}

如上程式碼,定義了一個委託 MyDel ,並且在呼叫的時候把Sum方法傳給了它的物件。一般情況下我們呼叫這個委託物件,它就會呼叫他呼叫列表中包含的方法。就想呼叫方法一樣,這是同步完成的。

但是如果委託物件在呼叫列表中只有一個方法(參照方法),它就可以非同步的去執行這個方法。BeginInovke和EndInvoke就是用來做這個事的。我們可以用如下的方式使用:

  • ①當我們呼叫BeginInvoke方法的時候,他開始在一個獨立的執行緒上執行參照方法,並且立即返回到原始執行緒。原始執行緒可以繼續,而參照方法會在想吃的執行緒中並行執行。
  • ②當程式希望獲取已完成的非同步方法的結果時,可以檢查BeginInvoke返回的IAsyncResult的IsCompleted屬性,或者呼叫委託的EndInvoke方法來等待委託執行完成。

上面的使用過程就引出的三種模式:

  • ①等待-直到完成 原始執行緒在發起了非同步方法以及做了一些其他處理之後,原始執行緒就中斷並且等待非同步方法執行完成之後再繼續。

  • ②輪詢 ,原始執行緒定期檢查發起的執行緒是否完成,如果沒有則可以繼續做其他的事情,

  • ③回撥 原始執行緒一直執行,無需等待或者檢查發起的執行緒是否完成,發起的執行緒中的參照發放完成之後,發起的執行緒會呼叫回撥方法,由回撥方法在呼叫的EndInvoke之前處理非同步方法的結果。

2.BeginInovke和EndInvoke詳細

對於 BeginInvoke 方法,有幾個注意的地方

① 我們可以根據上面的程式碼知道,BeginInvoke的引數包含如下兩個部分

  • 參照方法的引數
  • CallBack引數和State引數

②BeginInvoke 會線上程池中找到一個執行緒,讓參照方法執行在該執行緒上

③BeginInvoke 返回給呼叫執行緒一個實現IAsyncResult介面的物件的參照。這個介面參照包含了線上程池執行緒中執行的非同步方法的狀態。可以判斷這個狀態來確定非同步方法是否結束。

// 3和5是參照方法引數,兩個null分別是Callback引數和State引數
// iar是新執行緒的資訊
IAsyncResult iar = del.BeginInvoke(3, 5, null, null);

對於 EndInvoke 方法,有幾個注意的地方

①他的引數是上面BegionInvoke返回的IAsyncResult介面的參照物件,傳入這個物件是便於EndInvoke去找到參照方法執行的執行緒。並且這個引數置於參數列最後一個。EndInvoke提供了從非同步方法呼叫的所有輸出,包括ref和out引數。如果委託的參照方法有ref和out引數,他們必須包含在EndInvoke的參數列當中

IAsyncResult iar2 = del2.BeginInvoke(3, 5, out res, null, null);
del2.EndInvoke(out res, iar2);

②如果執行緒已經退出了,EndInvoke會做如下事情:

  • 清理退出的執行緒的狀態並且釋放資源
  • 找到參照方法的返回值,並作為自己的返回值

③如果EndInovke發現執行緒還在執行中,那麼呼叫執行緒就會停止並等待,直到清理完畢並返回值。

④因為EndInvoke會去清理執行緒資訊,所以BeginInvoke和EndInvoke必須成對使用。

⑤如果非同步方法出現異常,那麼在呼叫EndInvoke的時候會丟擲異常。

3.AsyncResult類

上面說BeginInvoke方法返回了一個IAsyncResult介面的參照物件(內部是AsyncResult類的物件),AsyncResult型別表現了非同步方法的狀態。下面是這類的主要組成部分:

4.三種模式

① 等待-直到完成 (比較簡單的模式)

//開始執行非同步方法
IAsyncResult iar = del.BeginInvoke(3, 5, null, null);
//Do Something 耗時
del2.EndInvoke(iar);

像上面的程式碼,BeginInvoke之後,做了一些事情,然後呼叫EndInvoke來處理結果,這種方式就是等待-直到完成的模式。

②輪詢模式

輪詢模式中,原始的執行緒發起了非同步的方法呼叫,做一些事情,然後使用IAsyncResult中的IsComplete熟悉來定期檢查開啟的執行緒是否完成。如果未完成就在去做一些其他事情。

delegate long MyDel(int first, int second);

class Program
{
    static long Sum(int x, int y)
    {
        Console.WriteLine("--Inside Sum@{0}", DateTime.Now.ToString());
        Thread.Sleep(200);
        return x + y;
    }

    static void Main(string[] args)
    {
        MyDel del = new MyDel(Sum);
        //開始執行非同步方法
        IAsyncResult iar = del.BeginInvoke(3, 5, null, null);
        //輪詢開始
        while (!iar.IsCompleted)
        {
            //未完成,執行下面的語句
            for (long i = 0; i < 20000000; i++)
                ;
        }
        //執行完成呼叫EndInvoke獲取結果
        long result = del.EndInvoke(iar);
        Console.ReadKey();
    }
}

③回撥模式

前兩種都是主動方式的,原始執行緒一直在監控這新開啟的執行緒。但是回撥是被動的,一旦原始執行緒發起了非同步方法,它就自己管自己了,不在考慮同步。

當非同步方法呼叫結束之後,系統呼叫一個使用者自定義的方法來處理結果,並且呼叫委託的EndInvoke方法。這個使用者自定義的方法就是回撥方法。

上面的BegionInvoke中寫過,他會有兩個引數一個Callback引數和一個State引數.

CallBack引數:是回撥方法的名稱。

State引數:可以是null,或者傳入回撥方法的一個物件的參照。我們可以用IAsyncResult引數的AsyncState屬性來獲取這個物件,引數的型別是object。

a.回撥方法:

回撥方法的簽名和返回型別必須和 AsyncCallback委託型別所描述的形式一致。

兩種方式,構建這個AsyncCallback引數

new AsyncCallback 物件  

IAsyncResult iar = del.BeginInvoke(3, 5, new AsyncCallback(CallWhenDone),null);

直接傳回撥方法的名稱

IAsyncResult iar = del.BeginInvoke(3, 5, CallWhenDone, null);

其中 CallWhenDone 如下:

static void CallWhenDone(IAsyncResult iar)
{
    AsyncResult ar = (AsyncResult)iar;
    MyDel del = (MyDel)ar.AsyncDelegate;
    //回撥方法中呼叫了EndInvoke
    long result = del.EndInvoke(iar);
}

b.在回撥方法中呼叫EndInvoke

上面程式碼中,在回撥中使用了EndInvoke,上文中說到 EndInvoke的呼叫,是委託的呼叫,並且需要傳入一個IAsyncResult的介面物件的參照。

所以想要在回撥方法裡面,呼叫這個EndInvoke,就得拿到兩個東西一個是委託物件、一個是IAsyncResult,由於我們AsyncCallback委託本身就是必須要傳入IAsyncResult 的,所以這個比較容易,剩下的就是委託物件本身了。在AsyncResult類小節裡面我看到,它裡面存著一個 AsyncDelegate(它就是委託物件的參照),還有就是 IAsyncResult介面物件在內部就是AsyncResult類物件。所以才可以像上main的程式碼,通過強制型別轉換得到MyDel的物件。

第二種方法就是如果State引數沒有用處,可以通過State引數,把委託的物件傳過去。

呼叫的地方,最後一個引數傳入del

IAsyncResult iar = del.BeginInvoke(3, 5, new AsyncCallback(CallWhenDone),del);

回撥方法:

static void CallWhenDone(IAsyncResult iar)
{
    MyDel del = (MyDel)iar.AsyncState;
    long result = del.EndInvoke(iar);
}

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對it145.com的支援。


IT145.com E-mail:sddin#qq.com