1. 程式人生 > >C#多執行緒的非同步委託/呼叫

C#多執行緒的非同步委託/呼叫

C#非同步呼叫(Asynchronou Delegate)

C#非同步呼叫獲取結果方法:主要有三種,也可以說是四種(官方說四種,電子書說三種),官方在MSDN上已經有詳細的說明: 連結

需要了解到獲取非同步執行的返回值,意味著你需要呼叫Delegate的BeginInvoke方法,而不是Invoke方法。

第一種就是書上沒有說的,但是官方還是給出來的,就是通過呼叫EndInvoke方法來獲取內容,檢視如下程式碼:

    class MyState
    {
        public int ThreadId = 0;
        public int Data = 0;

        public MyState()
        { 
            
        }
    }
    class AsyncInvoke
    {
        private MyState State = null;
        
        public AsyncInvoke()
        {
            State = new MyState();
        }

        private int TakesAWhile(int data, int ms)
        {
            Console.WriteLine("TakesAWhile started");
            Thread.Sleep(ms);
            Console.WriteLine("TakesAWhile completed");
            return ++data;
        }

        public delegate int TakesAWhileDelegate(int data, int ms);

        public void RunEndInvoke()
        {
            TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
            IAsyncResult ar = dl.BeginInvoke(5, 3000, null, null);
            int result = 0;
            result = dl.EndInvoke(ar);
            Console.WriteLine(string.Format("Result: {0}", result));
        }
    } 

輸出結果為:

TakesAWhile started
TakesAWhile completed
Result: 6

通過上面可以知道使用EndInvoke阻塞了主執行緒(RunEndInvoke函式),同時需要使用BeginInvoke的返回值ar作為EndInvoke的入參。

第二種方法Polling(輪詢)BeginInvoke的返回值(IAsyncResult中的屬性IsCompleted),將RunEndInvoke替換成下面的函式RunPolling:

        public void RunPolling()
        {
            TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
            IAsyncResult ar = dl.BeginInvoke(5, 3000, null, null);

            while (ar.IsCompleted == false)
            {
                Thread.Sleep(500);
            }
            int result = 0;
            result = dl.EndInvoke(ar);
            Console.WriteLine(string.Format("Result: {0}", result));
        }
結果同上,其實看到這裡你也發現第二種跟第一種沒有很大區別,所以書上沒有闡明第一種方法,其實如果只是為了獲取結果,用第一種方法就足夠了。其實這兩個例子沒有很好的體現BeginInvoke的非同步的優勢,好像跟使用Invoke方法沒有很大區別,都是事先了阻塞主執行緒,如下所示:
        public void RunInvoke()
        {
            TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
            int result = dl.Invoke(5, 3000);
            Console.WriteLine(string.Format("Result: {0}", result));
        }

其實這就涉及的Invoke以及BeginInvoke的區別了,因為呼叫Invoke,會阻塞主執行緒,等待非同步執行的完成(其實也沒有什麼非同步的說法了)。不過呼叫BeginInvoke則回立即返回,但是非同步執行的結果或者返回值如何獲得, 就是這部分闡述的目的:獲取非同步執行的返回值或結果,所以你可以將上述的RunBeginInvoke替換成如下的:
        public void RunEndInvoke()
        {
            TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
            IAsyncResult ar = dl.BeginInvoke(5, 3000, null, null);
            // Do others in main thread.
            Console.WriteLine("Do others in main thread...");
            //
            int result = 0;
            result = dl.EndInvoke(ar);
            Console.WriteLine(string.Format("Result: {0}", result));
        }
這樣輸出結果如下所示:

Do others in main thread...
TakesAWhile started
TakesAWhile completed
Result: 6

這樣你可以放別的工作在BeginInvoke的之後,在主執行緒執行,等到執行了,可以使用阻塞主執行緒來等待非同步執行的結果來進行下一步的需要,這樣情況能夠部分提高工作效率,通常用於執行相互獨立的模組,然後再等待兩邊結果(主執行緒以及非同步執行緒的結果)來進行下一步的操作。

好了,講第三種的方法來獲取非同步結果了,就是Wait Handle。 handle是通過BeginInvoke的返回值(ar)中的一個屬性AsyncWaitHandle來獲得的。一開始AsyncWaitHandle是沒有初始化的,但是隻要引用該屬性,作業系統實現它,同時呼叫其waitone函式去等待非同步執行完的訊號。如果使用沒有帶參的waitone函式,則無限期阻塞主執行緒來等待非同步執行,一旦執行完,則返回, 如下程式碼所示:

        public void RunWaitHandle()
        {
            TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
            IAsyncResult ar = dl.BeginInvoke(5, 3000, null, null);
            ar.AsyncWaitHandle.WaitOne();
            Console.WriteLine("Wait for 3 seconds.");
            int result = 0;
            result = dl.EndInvoke(ar);
            ar.AsyncWaitHandle.Close();
            Console.WriteLine(string.Format("Result: {0}", result));
        }

輸出結果基本同上,就多了一行“Wait for 3 seconds."來識別是否等待了3秒鐘,因為後面還有EndInvoke方法。

如果使用帶參的,如waitone(1000, false)來判斷當前返回值是true則意味著等待1秒,第二引數來判斷是否退出同步。

從上面三種方法來看,的確是挺好的,但是每次都需要主執行緒去等待,大哥不好當,所以就有了第四種方法,就是使用BegingInvoke的第三個引數,該函式是在BeginInvoke呼叫的函式執行完後自動執行,不然也不叫做callback。
第四個引數通常將非同步函式的delegate傳遞過去,這樣在callback函式中就可以方便的獲取到非同步函式的執行結果或者返回值。

        public void AsyncCompleted(IAsyncResult ar)
        {
            if (ar != null)
            {
				Console.WriteLine("After the async operation.");
                TakesAWhileDelegate dl = ar.AsyncState as TakesAWhileDelegate;
                int result = 0;
                result = dl.EndInvoke(ar);
                Console.WriteLine(string.Format("Result: {0}", result));
            }
        }

        public void RunAsyncCallback()
        {
            TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
            IAsyncResult ar = dl.BeginInvoke(5, 3000, AsyncCompleted, dl);
            Console.WriteLine("Finish RunAsyncCallback.");
        }

輸出結果:
Finish RunAsyncCallback.
Press any key to continue...
TakesAWhile started
TakesAWhile completed
After the async operation.
Result: 6

現在Lambda很流行,雖然還是不是很懂,但是也趕下潮流,可以將上述的callback方法改寫成lambda形式,同時可以不用指定關於BeginInvoke的第四個引數,同時可以在作用域中訪問非同步delegate(dl)。程式碼如下所示:
        public void RunAsyncCallback2()
        {
            TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
            dl.BeginInvoke(5, 3000, ar => {
                Console.Write("After async operation.");
                int result = 0;
                result = dl.EndInvoke(ar);
                Console.WriteLine(string.Format("Result: {0}", result));
            }, null);
            Console.WriteLine("Finish RunAsyncCallback.");
        }

至此,四種獲取非同步結果或者返回值的方法也說完了,現在補充一下別的知識,就是關於返回值的問題。
我們知道EndInvoke的呼叫或者Invoke的呼叫(這裡拋開了非同步的概念了)都只能返回一個值(真是廢話!)。如果我們想在非同步執行中修改多個值,可以選擇的方法有: 使用一個結構體或者類(簡言之就是一個複雜物件),將想要在非同步程式中修改物件整合在一起,就如下面的程式碼所示:
        private MyState TakesAWhile(int data)
        {
            Console.WriteLine("TakesAWhile started");
            Thread.Sleep(3000);
            Console.WriteLine("TakesAWhile completed");
            return new MyState(){Data=++data, ThreadId=Thread.CurrentThread.ManagedThreadId};
        }

        private delegate MyState TaskAWhileDelegate2(int data);

        public void RunEndInvokeWithStructReturn()
        {
            TaskAWhileDelegate2 dl = new TaskAWhileDelegate2(TakesAWhile);
            IAsyncResult ar = dl.BeginInvoke(5, null, null);
            MyState state = null;
            state = dl.EndInvoke(ar);
         
            Console.WriteLine(string.Format("Data: {0}, ThreadId: {1}", state.Data, state.ThreadId));
        }
輸出結果為:
TakesAWhile started
TakesAWhile completed
Data: 6, ThreadId: 6

使用複雜結構體或者類能夠實現多種引數的修改,一來節省輸入引數,二來想怎麼整合引數就怎麼整合。
其實另一種實用的方法(個人認為,也是從C++一路走來,巴拉巴拉。。。),使用c#中的ref來代替指標的,指向已經分配好的複雜物件也是一個很好的方法,如下程式碼實現同上的功能:
        private void TakesAWhile(int data, int ms, ref MyState state)
        {
            Console.WriteLine("TakesAWhile started");
            Thread.Sleep(ms);
            Console.WriteLine("TakesAWhile completed");
            state.Data = ++data;
            state.ThreadId = Thread.CurrentThread.ManagedThreadId;
        }

        private delegate void TakesAWhileDelegate3(int data, int ms, ref MyState state);
       
        public void RunEndInvokeWithRefStruct()
        {
            TakesAWhileDelegate3 dl = new TakesAWhileDelegate3(TakesAWhile);
            IAsyncResult ar = dl.BeginInvoke(5, 3000, ref State, null, null);
            dl.EndInvoke(ref State, ar); // this State could be any other reference to MyState
            Console.WriteLine(string.Format("Data: {0}, ThreadId: {1}", State.Data, State.ThreadId));
        }

其中EndInvke呼叫中需要多給一個引數,如果這個引數是成員變數,給或者不給都不會影響到後面一句的列印情況,也就是state已經在非同步函式中修改了,即使沒有明顯修改都沒有問題。如果使用out作為輸出引數,EndInvoke也需要額外的指定該引數,具體的實現就不給程式碼,因為實現沒有什麼好給的,而且本人不太好out這個傳遞,ref本命的人,希望能再別的地方尋找到使用out的有意義的用法,扯遠了。

到此為止,四種方法都講完了,非同步函式的輸入引數以及傳遞引數的方法以及技巧也講述了,總結一下:
獲取非同步函式的結果或者返回值的方法有:
1. EndInvoke呼叫,阻塞主執行緒
2. Polling IAsyncResult中IsCompleted屬性, 阻塞主執行緒,沒有什麼實際作用,浪費系統資源
3. Wait Handle響應,使用IAsyncResult中的AsyncWaitHandle屬性去呼叫handle的waitone函式,阻塞主執行緒,使用過載帶參函式實現是否等待同步或者直接跳出
4. 使用callback函式,並使其作為Begininvoke的第三個引數,使用非同步delegate作為第四個引數,以使callback能夠獲取非同步delegate的返回值。

使用心得:
如果需要跑兩個相互獨立的模組,或者主執行緒需要非同步執行的結果或者返回值,可以使用前面三種方法;如果後續操作不太依賴於非同步執行後的結果,可以使用callback,既能做到獲取處理結果,又不會影響主執行緒。

PS:

上面講述的主要是Thread或者task的非同步呼叫BeginInvoke的結果獲取方法,但是如果該非同步函式中涉及到輸出引數,就如剛才說的複雜物件中的成員是controller的話,就又有一章來講了,對於UI以及後臺執行緒的互動這是一個重要點,如果你不想你的UI經常假死的話,請收看下一回:Controller中BeginInvoke與Invoke的區別以及應用。

AD的優勢是非同步函式的強型別保證,引數的輸入都是規定好的(delegate宣告),如果需要傳遞引數可以在函式中使用ref關鍵字(只針對引用型別,值型別無效果)。由於非同步呼叫時使用task底層實現的,所以他具有了task的優勢,方便使用,但是也有其缺點,不能夠想thread那樣提供豐富的對執行緒設定(後臺,優先順序,不能中斷)。



相關推薦

C# 執行委託實現非同步_呼叫委託的BeginInvoke和EndInvoke方法

1.C#中的每一個委託都內建了BeginInvoke和EndInvoke方法,如果委託的方法列表裡只有一個方法,那麼這個方法就可以非同步執行(不在當前執行緒裡執行,另開闢一個執行緒執行)。委託的BeginInvoke和EndInvoke方法就是為了上述目的而生的。 2.原始

C# 執行委託的疑惑 InvokeRequired==true or false

我們經常會碰到跨執行緒訪問的問題,這時候可行的辦法就是用委託,但是最近在用到委託的時候,發現InvokeRequired的值在程式執行過程中會變來變去的,我本來以為這個是一個定值,當InvokeRequired值為true的時候,就說明現在遇到跨執行緒訪問了,那下面就需要用到

C# 執行 非同步

一、基本概念 1、程序 首先開啟工作管理員,檢視當前執行的程序: 從工作管理員裡面可以看到當前所有正在執行的程序。那麼究竟什麼是程序呢? 程序(Process)是Windows系統中的一個基本概念,它包含著一個執行程式所需要的資源。一個正在執行的應用程式在作業

C#執行非同步訪問winform中控制元件

我們在做winform應用的時候,大部分情況下都會碰到使用多執行緒控制介面上控制元件資訊的問題。然而我們並不能用傳統方法來做這個問題,下面我將詳細的介紹。       首先來看傳統方法:      public partial class Form1 : Form    

c#執行委託

一:執行緒 在.net中提供了兩種啟動執行緒的方式,一種是不帶引數的啟動方式,另一種是帶引數的啟動的方式。不帶引數的啟動方式  如果啟動引數時無需其它額外的資訊,可以使用ThreadStart來例項化Thread:  帶引數的啟動方法帶引數,就不能用ThreadStart委

C#執行非同步委託/呼叫

C#非同步呼叫(Asynchronou Delegate) C#非同步呼叫獲取結果方法:主要有三種,也可以說是四種(官方說四種,電子書說三種),官方在MSDN上已經有詳細的說明: 連結 需要了解到獲取非同步執行的返回值,意味著你需要呼叫Delegate的BeginIn

C# 執行詳解 Part.02(UI 執行和子執行的互動、ProgressBar 的非同步呼叫

       我們先來看一段執行時會丟擲 InvalidOperationException 異常的程式碼段: private void btnThreadA_Click(object sender, EventArgs e) { Thread thread

2017.10.12 C#執行非同步的區別

最近在寫個多執行緒處理的程式,又重新溫習了一下相關知識,記錄在這裡。 C#多執行緒與非同步的區別 原文地址:http://kb.cnblogs.com/page/116095/ 多執行緒和非同步操作的異同   多執行緒和非同步操作兩者都可以達到避免呼叫執行緒阻塞的目的,從而提高軟體

C# 執行呼叫靜態方法或者靜態例項中的同一個方法-方法內部的變數是執行安全的

 C#  多執行緒呼叫靜態方法或者靜態例項中的同一個方法-方法內部的變數是執行緒安全的       using System;using System.Threading;using System.Threading.Tasks;using Sys

C#執行呼叫有參的方法

Thread (ParameterizedThreadStart) 初始化 Thread 類的新例項,指定允許物件線上程啟動時傳遞給執行緒的委託。    Thread (ThreadStart) 初始化 Thread 類的新例項。   由 .NET Compact Fra

C# 實現的執行非同步Socket資料包接收器框架

幾天前在博問中看到一個C# Socket問題,就想到筆者2004年做的一個省級交通流量接收伺服器專案,當時的基本求如下: 接收自動觀測裝置通過無線網絡卡、Internet和Socket上報的交通量資料包 全年365*24執行的自動觀測裝置5分鐘上報一次觀測資料,每筆記錄約

C/C++函式的本質以及執行函式的呼叫過程

C/C++中,函式的本質是一段可執行程式碼,程式碼包括了局部變數、全域性變數的地址等等。到組合語言的級別,變數函式等都可以視為彙編的程式碼片段。函式的本質就是一個可執行程式碼片段的集合 執行緒的詳細介紹:http://www.cnblogs.com/tracylee/archive/

C#執行程式設計筆記(5.5)-處理非同步操作中的異常

近來在學習Eugene Agafonov編寫的《C#多執行緒程式設計實戰》(譯),做些筆記也順便分享一下^-^本篇將描述在C#中使用非同步函式時如何處理異常。我們將學習對多個並行的非同步操作使用await時如何聚合異常。using System; using System.T

C#執行非同步的區別

原文地址:http://kb.cnblogs.com/page/116095/  隨著擁有多個硬執行緒CPU(超執行緒、雙核)的普及,多執行緒和非同步操作等併發程式設計方法也受到了更多的關注和討論。本文主要是想與園中各位高手一同探討一下如何使用併發來最大化程式的效能。   多執行緒和非同步操作的異同  

C++ 執行呼叫Python指令碼

由於Python直譯器有全域性解釋所GIL的原因,導致在同一時刻只能有一個執行緒擁有直譯器,所以在C++多執行緒呼叫python指令碼時,需要控制GIL,執行緒獲取GIL。 在主執行緒中初始化Python直譯器環境,程式碼如下: { Py_Initialize()

c#執行:執行池和非同步程式設計

我們將在這裡進一步討論一些.NET類,以及他們在多執行緒程式設計中扮演的角色和怎麼程式設計。它們是:  System.Threading.ThreadPool 類  System.Threading.Timer 類  如果執行緒的數目並不是很多,而且你想控制每個執行緒的細節諸

C# 在執行中如何呼叫Winform

問題的產生:  我的WinForm程式中有一個用於更新主視窗的工作執行緒(worker thread),但文件中卻提示我不能在多執行緒中呼叫這個form(為什麼?),而事實上我在呼叫時程式常常會崩掉。請問如何從多執行緒中呼叫form中的方法呢?   解答:  每一個從Con

C#執行】2.執行池簡述+兩種傳統的非同步模式

執行緒池簡述+兩種傳統的非同步程式設計模式 1.執行緒池簡述   首先我們要明確一點,程式設計中講的執行緒與平時我們形容CPU幾核幾線程中的執行緒是不一樣的,CPU執行緒是指邏輯處理器,比如4核8執行緒,講的是這個cpu有8個邏輯處理器,可以同時處理8個執行緒。我們程式設計中講的執行緒在計算機中可以有許多許多

C#執行非同步

1、什麼是非同步同步 如果一個方法被呼叫,呼叫者需要等待該方法被執行完畢之後才能繼續執行,則是同步。 如果方法被呼叫後立刻返回,即使該方法是一個耗時操作,也能立刻返回到呼叫者,呼叫者不需要等待該方法,則稱之為非同步。 非同步程式設計需要用到Task任務函式,不返回值的任務由 System.Threading.

談談C#執行開發:並行、併發與非同步程式設計

閱讀導航 一、使用Task 二、並行程式設計 三、執行緒同步 四、非同步程式設計模型 五、多執行緒資料安全 六、異常處理   概述 現代程式開發過程中不可避免會使用到多執行緒相關的技術,之所以要使用多執行緒,主要原因或目的大致有以下幾個: 1、 業務特性決定程式就是多工的,比如,一邊採集資料、一邊分