C#已有10多年历史,单从微软2年一版的更新进度来看活力异常旺盛,C#中的异步编程也经历了多个版本的演化,从今天起着手写一个系列博文,记录一下C#中的异步编程的发展历程。广告一下:喜欢我文章的朋友,请点下面的“关注我”。谢谢html
我是2004年接触并使用C#的,那时C#版本为1.1,因此咱们就从就那个时候谈起。那时在大学里本身看书写程序,所写的程序大都是同步程序,最多启动个线程........其实在C#1.1的时代已有完整的异步编程解决方案,那就是APM(异步编程模型)。若是还有不了解“同步程序、异步程序”的请自行百度哦。编程
APM异步编程模型最具表明性的特色是:一个异步功能由以Begin开头、End开头的两个方法组成。Begin开头的方法表示启动异步功能的执行,End开头的方法表示等待异步功能执行结束并返回执行结果。下面是一个模拟的实现方式(后面将编写标准的APM模型异步实现):多线程
public class Worker { public int A { get; set; } public int B { get; set; } private int R { get; set; } ManualResetEvent et; public void BeginWork(Action action) { et = new ManualResetEvent(false); new Thread(() => { R = A + B; Thread.Sleep(1000); et.Set(); if(null != action) { action(); } }).Start(); } public int EndWork() { if(null == et) { throw new Exception("调用EndWork前,须要先调用BeginWork"); } else { et.WaitOne(); return R; } } }
static void Main(string[] args) { Worker w = new Worker(); w.BeginWork(()=> { Console.WriteLine("Thread Id:{0},Count:{1}", Thread.CurrentThread.ManagedThreadId, w.EndWork()); }); Console.WriteLine("Thread Id:{0}", Thread.CurrentThread.ManagedThreadId); Console.ReadLine(); }
在上面的模拟APM模型中咱们使用了 Thread、ManualResetEvent,若是你对多线程和ManualResetEvent不熟悉,请阅读我以前的Blog《C#多线程的用法系列-线程间的协做ManualResetEvent》。C#中使用异步编程不可避免的会涉及到多线程的知识,虽然微软在Framework中作了不少封装,但朋友们应该掌握其本质。异步
上面模拟的APM异步模型之因此简单,是由于C#发展过程当中引入了不少优秀的语法规则。上例咱们较多的使用了Lambda表达式,若是你不熟悉 匿名委托与lambda表达式可看我以前的Bolg《匿名委托与Lambda表达式》。上面作了如此多的广告,下面咱们来看一下标准的APM模型如何实现异步编程。async
IAsyncResult接口异步编程
IAsyncResult接口定义了异步功能的状态,该接口具体属性及含义以下:post
// 表示异步操做的状态。 [ComVisible(true)] public interface IAsyncResult { // // 摘要: // 获取一个值,该值指示异步操做是否已完成。 // // 返回结果: // 若是操做已完成,则为 true;不然为 false。 bool IsCompleted { get; } // // 摘要: // 获取用于等待异步操做完成的 System.Threading.WaitHandle。 // // 返回结果: // 用于等待异步操做完成的 System.Threading.WaitHandle。 WaitHandle AsyncWaitHandle { get; } // // 摘要: // 获取一个用户定义的对象,该对象限定或包含有关异步操做的信息。 // // 返回结果: // 一个用户定义的对象,限定或包含有关异步操做的信息。 object AsyncState { get; } // // 摘要: // 获取一个值,该值指示异步操做是否同步完成。 // // 返回结果: // 若是异步操做同步完成,则为 true;不然为 false。 bool CompletedSynchronously { get; } }
注意:模型示例1中的 ManualResetEvent 继承自 WaitHandle
APM传说实现方式
在了解了IAsyncResult接口后,咱们来经过实现 IAsyncResult 接口的方式完成对模拟示例的改写工做,代码以下:
public class NewWorker { public class WorkerAsyncResult : IAsyncResult { AsyncCallback callback; public WorkerAsyncResult(int a,int b, AsyncCallback callback, object asyncState) { A = a; B = b; state = asyncState; this.callback = callback; new Thread(Count).Start(this); } public int A { get; set; } public int B { get; set; } public int R { get; private set; } private object state; public object AsyncState { get { return state; } } private ManualResetEvent waitHandle; public WaitHandle AsyncWaitHandle { get { if (null == waitHandle) { waitHandle = new ManualResetEvent(false); } return waitHandle; } } private bool completedSynchronously; public bool CompletedSynchronously { get { return completedSynchronously; } } private bool isCompleted; public bool IsCompleted { get { return isCompleted; } } private static void Count(object state) { var result = state as WorkerAsyncResult; result.R = result.A + result.B; Thread.Sleep(1000); result.completedSynchronously = false; result.isCompleted = true; ((ManualResetEvent)result.AsyncWaitHandle).Set(); if (result.callback != null) { result.callback(result); } } } public int Num1 { get; set; } public int Num2 { get; set; } public IAsyncResult BeginWork(AsyncCallback userCallback, object asyncState) { IAsyncResult result = new WorkerAsyncResult(Num1,Num2,userCallback, asyncState); return result; } public int EndWork(IAsyncResult result) { WorkerAsyncResult r = result as WorkerAsyncResult; r.AsyncWaitHandle.WaitOne(); return r.R; } }
示例代码分析:this
上面代码中NewWorker的内部类 WorkerAsyncResult 是关键点,它实现了 IAsyncResult 接口并由它来负责开启新线程完成计算工做。spa
在WorkerAsyncResult中增长了 A、B两个私有属性来存储用于计算的数值,一个对外可读不可写的属性R,用于存储WorkerAsyncResult内部运算的结果。AsyncWaitHandle属性由 ManualResetEvent 来充当,并在首次访问时建立ManualResetEvent(但不释放)。其余接口属性正常实现,没有什么可说。线程
WorkerAsyncResult 中新增 static Count 方法,参数 state 为调用Count方法的当前WorkerAsyncResult对象。Count 方法在 WorkerAsyncResult 对象的新启线程中运行,所以Thread.Sleep(1000)将阻塞新线程1秒中。而后设置当前WorkerAsyncResult对象是否同步完成为false,异步完成状态为true,释放ManualResetEvent通知以便等待线程获取通知进入执行状态,判断是否有异步执行结束回调委托,存在则回调之。
NewWorker 很是简单,Num一、Num2两个属性为要计算的数值。BeginWork 建立WorkerAsyncResult对象、并将要计算的两个数值Num一、Num二、userCallback回调委托、object 类型的 asyncState 传入要建立的WorkerAsyncResult对象。通过此步操做,WorkerAsyncResult对象获取了运算所需的全部数据、运算完成后的回调,并立刻启动新线程进行运算(执行WorkerAsyncResult.Count方法)。
由于WorkerAsyncResult.Count执行在新线程中,在该线程外部没法准确获知新线程的状态。为了知足外部线程与新线程同步的需求,在NewWorker中增长EndWork方法,参数类型为IAsyncResult。要调用EndWork方法应传入BeginWork 获取的WorkerAsyncResult对象,EndWork方法获取WorkerAsyncResult对象后,调用WorkerAsyncResult.AsyncWaitHandle.WaitOne()方法,等待获取ManualResetEvent通知,在获取到通知时运算线程已运算结束(线程并未结束),下一步获取运算结果R并返回。
接下来是NewWorker调用程序,以下:
static void Main(string[] args) { NewWorker w2 = new NewWorker(); w2.Num1 = 10; w2.Num2 = 12; IAsyncResult r = null; r = w2.BeginWork((obj) => { Console.WriteLine("Thread Id:{0},Count:{1}",Thread.CurrentThread.ManagedThreadId, w2.EndWork(r)); }, null); Console.WriteLine("Thread Id:{0}", Thread.CurrentThread.ManagedThreadId); Console.ReadLine(); }
下图我简单画的程序调用过程,有助于各位朋友理解:
标准的APM模型异步编程,对应开发人员来讲过于复杂。所以经过实现 IAsyncResult 接口进行异步编程,就是传说中的中看不中用(罪过罪过.....)。
Delegate异步编程(APM 标准实现)
C#中委托天生支持异步调用(APM模型),任何委托对象后"."就会发现BeginInvoke、EndInvoke、Invoke三个方法。BeginInvoke为异步方式调用委托、EndInvoke等待委托的异步调用结束、Invoke同步方式调用委托。所以上面的标准APM实例,可借助 delegate 进行以下简化。
上面NewWorker使用委托方式改写以下:
public class NewWorker2 { Func<int, int, int> action; public NewWorker2() { action = new Func<int, int, int>(Work); } public IAsyncResult BeginWork(AsyncCallback callback, object state) { dynamic obj = state; return action.BeginInvoke(obj.A, obj.B, callback, this); } public int EndWork(IAsyncResult asyncResult) { try { return action.EndInvoke(asyncResult); } catch (Exception ex) { throw ex; } } private int Work(int a, int b) { Thread.Sleep(1000); return a + b; } }
调用程序:
static void Main(string[] args) { NewWorker2 w2 = new NewWorker2(); IAsyncResult r = null; r = w2.BeginWork((obj) => { Console.WriteLine("Thread Id:{0},Count:{1}", Thread.CurrentThread.ManagedThreadId, w2.EndWork(r)); }, new { A = 10, B = 11 }); Console.WriteLine("Thread Id:{0}", Thread.CurrentThread.ManagedThreadId); Console.ReadLine(); }
上面的使用委托进行APM异步编程,比实现 IAsyncResult 接口的方式精简太多、更易理解使用。所以这里建议朋友们 delegate 异步调用模型应该掌握起来,而经过实现 IAsyncResult 接口的传说方式看你的喜爱吧。
同时上面的delegate中咱们还用了一些委托、匿名对象、动态类型等知识,若是你感兴趣的话均可从个人Blog中找到相关知识供你参考。
最后再广告一次:喜欢我文章的朋友请关注一下个人blog,谢谢。