2019年4月7日 星期日

C# 如何設計具有事件 event 的回呼 callback 的類別

C# 如何設計具有事件 event 的回呼 callback 的類別

在這篇文章將會來說明如何設計出一個具有 事件 Event 和 回呼 Callback 委派機制的類別,這樣的類別是可以於值行一個非同步的工作,不過,透過這個類別的設計過程,將會了解到一些技術底層做法,因此將會有助於了解 C# async await 這類非同步工作的運作機制與相關除錯的問題。
首先,對於要提供一個具有 事件 Event 和 回呼 Callback 委派機制的類別,必須至少要提供兩個功能,第一個就是要啟動非同步工作的方法與當非同步工作完成之後要執行的委派事件方法。在這個範例中,將會建立一個 MyAsyncClass 類別,在這個類別中,有提供 DoRun() 方法,這個方法將會使用執行緒集區 ThreadPool 取得一個背景執行緒,並且會在這個背景執行緒中執行需要花費比較多時間才能夠完成事情,因此,一旦在執行緒 A 呼叫這個 DoRun() 方法之後,執行緒A便可以繼續執行其他的程式碼,而 DoRun() 方法內所產生的背景執行緒,將會並行執行中,也就是說,同時有兩個執行緒在執行中。
另外一個功能就是要提供的事件,這裡宣告 OnCompletion 的型別為 EventHandler,這也就是我們一般在 .NET 中使用的 事件 機制所用到的型別,而這個 OnCompletion 事件將會於 DoRun() 方法內的背景執行緒中用到;當背景執行緒內的程式碼都執行完成之後,便需要檢查呼叫這個非同步方法 (DoRun) 的呼叫端,是否有定義至少一個事件委派方法到 OnCompletion 公開欄位內,若有的話,將會需要執行這些委派方法,在此使用的 C# 敘述為 OnCompletion?.Invoke(this, EventArgs.Empty); 。在這裡要特別注意的是,執行 OnCompletion?.Invoke(this, EventArgs.Empty); 這個敘述的時候是在背景執行緒下,而不是在呼叫端的執行緒下,雖然該事件委派方法是定義在呼叫端的類別內,這並不代表非同步事件內,當非同步作業完成後,要執行的事件委派方法就會在呼叫端的執行緒內,理由很簡單,若沒有特別的同步機制存在,程式設計很難就 .NET 預設提供的功能內,做到在 執行緒B 內,可以指定一段程式碼,指定在執行緒A 下來執行,這是做不到的。
在呼叫端會先建立一個 CallbaskNThread 物件,接著使用 myAsyncObject.OnCompletion 事件欄位,搭配 Lambda 來定義當非同步事件完成後要執行的方法,最後,執行 DoWork 便開始進行非同步作業了。
底下是這個講解範例程式碼執行輸出結果,從這些內容將可以確認,事件委派方法並不是在主執行緒下來執行的。
Main方法內的執行緒ID=1
Main方法內的開始呼叫 DoRun 方法的執行緒ID=1
執行 DoRun 前的執行緒ID=1
Press any key for continuing...
進入到非同步執行緒內的執行緒ID=3
模擬需要3秒鐘的非同步工作
準備要呼叫 callback,現在的執行緒ID=3
在 Main 方法內的委派 callback ,現在的執行緒ID=3
callback 執行結束了
C Sharp / C#
namespace CallbackNThread
{
    class MyAsyncClass
    {
        public EventHandler OnCompletion;
        public void DoRun()
        {
            Console.WriteLine($"執行 DoRun 前的執行緒ID={Thread.CurrentThread.ManagedThreadId}");
            ThreadPool.QueueUserWorkItem(x =>
            {
                Console.WriteLine($"進入到非同步執行緒內的執行緒ID={Thread.CurrentThread.ManagedThreadId}");
                Console.WriteLine("模擬需要3秒鐘的非同步工作");
                Thread.Sleep(3000);

                Console.WriteLine($"準備要呼叫 callback,現在的執行緒ID={Thread.CurrentThread.ManagedThreadId}");
                OnCompletion?.Invoke(this, EventArgs.Empty);
            });
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Main方法內的執行緒ID={Thread.CurrentThread.ManagedThreadId}");
            MyAsyncClass myAsyncObject = new MyAsyncClass();
            myAsyncObject.OnCompletion += (s, e) =>
            {
                Console.WriteLine($"在 Main 方法內的委派 callback ,現在的執行緒ID={Thread.CurrentThread.ManagedThreadId}");
                Thread.Sleep(1000);
                Console.WriteLine("callback 執行結束了");
            };
            Console.WriteLine($"Main方法內的開始呼叫 DoRun 方法的執行緒ID={Thread.CurrentThread.ManagedThreadId}");
            myAsyncObject.DoRun();

            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }
    }
}


沒有留言:

張貼留言