.Net Core自实现CLR异步编程模式(Asynchronous programming patterns)

最近在看一个线程框架,对.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关键字会将代码分为同步和回调,这个模式的实现还是需要反编译源码去知道编译器做了哪些动作。以后有时间我会和大家探讨一下这其中的原理。

好了今天就写到这里了,如果大家有任何不明白的地方欢迎评论留言,最后谢谢大家的阅读。

原文地址:https://www.cnblogs.com/neilhu/p/14844332.html