最近在看一個執行緒框架,對.Net的非同步程式設計模型很感興趣,所以在這裡實現CLR定義的非同步程式設計模型,在CLR裡有三種非同步模式如下,如果不瞭解的可以詳細看MSDN 文件 Asynchronous programming patterns。
1.Asynchronous Programming Model (APM)非同步程式設計模式(也叫 IAsyncResult 模式),
public class MyClass { public IAsyncResult BeginRead(byte [] buffer, int offset, int count,AsyncCallbackcallback, object state); public int EndRead(IAsyncResult asyncResult); }
2.Event-based Asynchronous Pattern (EAP)基於事件的非同步模式(客戶端應用程式善於)
public class MyClass { public void ReadAsync(byte [] buffer, int offset, int count); public event ReadCompletedEventHandler ReadCompleted; }
3.Task-based Asynchronous Pattern (TAP)基於任務的非同步模式(async 和await關鍵字)
public class MyClass { public Task<int> ReadAsync(byte [] buffer, int offset, int count); }
現在我們基於第一種模式APM模式來自己實現一個非同步模式,首先我們需要接觸APM的一個重要介面IAsyncResult,他有四個屬性需要實現。
namespace System { public interface IAsyncResult { object? AsyncState { get; } WaitHandle AsyncWaitHandle { get; } bool CompletedSynchronously { get; } bool IsCompleted { get; } } }
這四個物件分別有著自己的功能,IsCompleted是為了輪詢查詢狀態,AsyncWaitHandle 是為了執行緒同步,AsyncState 是為了回撥技術。擁有了這三個物件就可以做一個非同步機制。首先我們實現這個介面。
public class DelayTaskAsyncResult : IAsyncResult { private AsyncCallback _callback; private object _asyncState; private ManualResetEvent _resetEvent = new ManualResetEvent(false); public object result { get; set; } public DelayTaskAsyncResult(AsyncCallback callback, object state) { this._callback = callback; this._asyncState = state; } public volatile int _completed = 0; public void SetCompleted() { Interlocked.Increment(ref _completed); _resetEvent.Set(); _callback?.Invoke(this); } public object EndInvoke() { if (!IsCompleted) { AsyncWaitHandle.WaitOne(); } return result; } public object AsyncState => _asyncState; public WaitHandle AsyncWaitHandle => _resetEvent; public bool CompletedSynchronously => throw new NotImplementedException(); public bool IsCompleted => _completed != 0; }
很簡單的實現如上,首先來解釋一下這段程式碼,_callback和_asyncState是作為回撥技術使用的,_resetEvent是為了執行緒同步技術使用的,result介面是非同步處理後得到的結果,_completed作為執行緒處理狀態的標記,加了volatile保證原子性保證多執行緒模式下拿到的值是最新的,SetCompleted方法是線上程執行完畢之後執行更新IAsyncResult其中的狀態,先將狀態值_completed自增,然後設定通過的訊號量,有回撥方法執行回撥,而EndInvoke方法中如果沒有執行完就等待訊號量,如果執行完就返回執行結果。
現在介面已經實現完成,現在需要定義自己想要的任務物件,在這裡我模擬了一個非同步物件線上程裡做一些耗時操作如下。
public class DelayTask { public int _seconds { get; set; } public DelayTask(int seconds) { _seconds = seconds; } public IAsyncResult BeginDelay(AsyncCallback callback,object state) { var result = new DelayTaskAsyncResult(callback,state); ThreadPool.QueueUserWorkItem(_delayCore, result); return result; } public object EndDelay(IAsyncResult asyncResult) { var result = (DelayTaskAsyncResult)asyncResult; return result.EndInvoke(); } private void _delayCore(object obj) { var asyncResult = (DelayTaskAsyncResult)obj; Thread.Sleep(_seconds * 1000); asyncResult.result = DateTime.Now; asyncResult.SetCompleted(); } }
在DelayTask裡,BeginDelay接受兩個引數AsyncCallback和object,這兩個引數是為了回撥機制使用的,然後建立一個非同步結果DelayTaskAsyncResult傳入另一個執行緒執行_delayCore,在_delayCore執行一個耗時操作然後將結果賦予result物件並更新狀態SetCompleted,在EndDelay裡,呼叫EndInvoke去同步非同步結果。
使用方式如下
public static void Main(string[] args) { DelayTask task = new DelayTask(5); var asyncResult = task.BeginDelay(null, null); Console.WriteLine("main execute"); Console.WriteLine("other end at " + task.EndDelay(asyncResult)); Console.Read(); } //execute result: //main execute //consume time operation //other end at 2021/6/3 20:51:18
這個實現了非同步操作並沒有block main thread,直到呼叫EndDelay block得到執行結果。下一步再看一下非同步回撥方法的使用。
public static void Main(string[] args) { DelayTask task = new DelayTask(5); var asyncResult = task.BeginDelay(TaskCompleteCallBack, task); Console.WriteLine("main execute"); Console.Read(); } private static void TaskCompleteCallBack(IAsyncResult ar) { var task = (DelayTask)ar.AsyncState; Console.WriteLine("other end at " + task.EndDelay(ar)); }
效果和上面一樣,值得注意的是非同步的時候回撥方法是執行在另一個執行緒上。 好了,APM的模式實現我們已經完成了。
現在我們看第二種的EAP的實現方式,基於事件的非同步程式設計模式。這在富客戶端應用程式大展拳腳。他的實現非常簡單。
public delegate void TaskCompletedEventHandler(object sender, TaskCompletedEventArg e); public class DelayTask1 { private int _seronds; public DelayTask1(int seronds) { _seronds = seronds; } public event TaskCompletedEventHandler TaskCompletedEventHandler; public void DoTaskAsync(string str) { ThreadPool.QueueUserWorkItem(TaskHelper,str); } private void TaskHelper(object state) { var text = (string)state; Thread.Sleep(_seronds*1000); var result = DateTime.Now.ToString("yyyy-mm-dd")+text; TaskCompletedEventHandler.Invoke(this,new TaskCompletedEventArg { Result= result }); } }
首先定義一個委託,然後用這個委託宣告事件,委託定義了一個事件引數是為了回撥使用,然後TaskHelper就是非同步執行的方法,基於事件的實現因為沒有非同步物件IAsyncResult實現的非常清晰。呼叫如下。
public static void Main(string[] args) { var task = new DelayTask1(5); task.TaskCompletedEventHandler += TaskCompleteCallBack; task.DoTaskAsync(" by neil"); Console.WriteLine("main execute"); Console.Read(); } private static void TaskCompleteCallBack(object sender, TaskCompletedEventArg e) { Console.WriteLine("other end at"+ e.Result); } //main execute //consume time operation //other end at2021-11-03 by neil
EAP模式的例子非常清晰,大家可以執行就可。
現在我們使用第三種的TAP的非同步程式設計模型非常多,不管是富客戶端還是asp.net core中,這是因為編譯器在中間做了大量的工作,async和await關鍵字會將程式碼分為同步和回撥,這個模式的實現還是需要反編譯原始碼去知道編譯器做了哪些動作。以後有時間我會和大家探討一下這其中的原理。
好了今天就寫到這裡了,如果大家有任何不明白的地方歡迎評論留言,最後謝謝大家的閱讀。