.Net Core自實現CLR非同步程式設計模式(Asynchronous programming patterns)

NeilHu發表於2021-06-03

最近在看一個執行緒框架,對.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關鍵字會將程式碼分為同步和回撥,這個模式的實現還是需要反編譯原始碼去知道編譯器做了哪些動作。以後有時間我會和大家探討一下這其中的原理。

好了今天就寫到這裡了,如果大家有任何不明白的地方歡迎評論留言,最後謝謝大家的閱讀。

 

相關文章