.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关键字会将代码分为同步和回调,这个模式的实现还是需要反编译源码去知道编译器做了哪些动作。以后有时间我会和大家探讨一下这其中的原理。
好了今天就写到这里了,如果大家有任何不明白的地方欢迎评论留言,最后谢谢大家的阅读。