作为模式,只是一种大家认可的经验,模式可以作为大家交流的词汇而存在。下面我们就要介绍几种异步编程模式,AMP、EAP和TAP。当然,法无定法,在我们理解的基础上,我们可以根据具体情况适度修改。下面介绍的只是在通常情况下的两种模式会是以什么样子的形式出现。
一 模型简介
1.APM 异步编程模型
这种模式的特征是一些成对出现的方法,分别以Begin和End作为前缀。
2.EAP 基于事件的异步模式
这个模式通常用于面向UI的组件,其中这些组件必须和进度报告和取消操作关联起来。方法有Async前缀。这种模式通常实现起来更为复杂,并且会带来一些额外开销,比如要由线程池线程转换到UI线程。
3.TAP 基于任务的模式
二 AMP模型
AMP的实现过程可以分为3个步骤,前两个步骤很明显,就是
1.编写BeginXXX方法,例如
public IAsyncResult BeginWork(int sleepyTime, AsyncCallback callback, object state)
最后两个参数是模式必须的,一个代表完成时的回调,一个代表要传递给回调的对象。因为AsyncCallback的定义是这样的
public delegate void AsyncCallback ( IAsyncResult ar )
所以你没办法直接给回调方法传递自己想要的参数,只能通过state来间接的传递给回调方法。
2.编写EndXXX方法
注意,EndXXX方法不等于回调方法,EndFoo方法应该是在回调方法里被调用来获取执行结果,这是刚接触的学习者常弄混的概念。它返回的结果应该和同步的XXX方法是一样的。下面展示了一个IAsyncResult的End方法的通常实现方式:
public T End() { // Wait for the work to finish, if it hasn't already. if (_isCompleted == 0) { _asyncWaitHandle.WaitOne(); _asyncWaitHandle.Close(); } // Propagate any exceptions or return the result. if (_exception != null) throw _exception; return _result; }
由于异步会立即返回,因此立即返回的对象是一个实现了IAsyncResult接口的对象,不是我们真正所需要的结果。那么AMP如何获取我们真正想要的结果呢?有下面这四种形式:
-
1.从应用程序的主线程调用EndXXX方法,阻止应用程序执行,直到操作完成之后再继续执行。比如:
IAsyncResult ar = foo.BeginWork(null, null); var result = foo.EndWork(ar);
-
2.使用 AsyncWaitHandle 来阻止应用程序执行,直到一个或多个操作完成之后再继续执行。
-
3.按以下方式轮询操作完成状态:定期检查 IsCompleted 属性,操作完成后调用 End方法。
-
4.使用 AsyncCallback 委托来指定操作完成时要调用的方法。
上面这几种方式都和一个实现了IAsyncResult接口的对象有关,所以第三步就是
3.编写一个实现IAsyncResult接口的类把BeginXXX和EndXXX这两个方法关联起来。
需要注意的是,前面三种方式主要利用了内核同步对象,会引起线程阻塞。我们主要关注第四种以回调的形式,这才是APM最常用的方式。
我们打算自己实现以下IAsyncResult来更好的说明这四种方式, 在动手实现之前,先看看IAsyncResult接口的定义
public interface IAsyncResult { object AsyncState { get; } WaitHandle AsyncWaitHandle { get; } bool CompletedSynchronously { get; } bool IsCompleted { get; } }
实现IAsyncResult
public delegate T Func<T>(); public class SimpleAsyncResult<T> : IAsyncResult { // All of the ordinary async result state. private volatile int _isCompleted; // 0==not complete, 1==complete. private readonly ManualResetEvent _asyncWaitHandle; private readonly AsyncCallback _callback; private readonly object _asyncState; private Exception _exception; private T _result; public SimpleAsyncResult( Func<T> work, AsyncCallback callback, object state) { _callback = callback; _asyncState = state; _asyncWaitHandle = new ManualResetEvent(false); RunWorkAsynchronously(work); } public bool IsCompleted { get { return (_isCompleted == 1); } } public WaitHandle AsyncWaitHandle { get { return _asyncWaitHandle; } } public object AsyncState { get { return _asyncState; } } // Runs the work on the thread pool, capturing exceptions, // results, and signaling completion. private void RunWorkAsynchronously(Func<T> work) { ThreadPool.QueueUserWorkItem(delegate { try { _result = work(); } catch (Exception e) { _exception = e; } finally { // Signal completion in the proper order: _isCompleted = 1; _asyncWaitHandle.Set(); if (_callback != null) _callback(this); } }); } public T End() { // Wait for the work to finish, if it hasn't already. if (_isCompleted == 0) { _asyncWaitHandle.WaitOne(); _asyncWaitHandle.Close(); } // Propagate any exceptions or return the result. if (_exception != null) throw _exception; return _result; } }
如何使用我们上面所编写的类来实现异步方法:
public class SimpleAsyncOperation { public int Work(int sleepyTime) { Thread.Sleep(sleepyTime); return new Random().Next(); } public IAsyncResult BeginWork( int sleepyTime, AsyncCallback callback, object state) { return new SimpleAsyncResult<int>( delegate { return Work(sleepyTime); }, callback, state ); } public int EndWork(IAsyncResult asyncResult) { SimpleAsyncResult<int> simpleResult = asyncResult as SimpleAsyncResult<int>; if (simpleResult == null) throw new ArgumentException("Bad async result."); return simpleResult.End(); } }
构造函数的参数包含了一个Func<T>委托,这个委托表示需要通过异步方式来完成的工作。我们在私有方法RunWorkAsynchronously中使用线程池来实现了异步运行,并且是在一个try块中运行。如果执行成功,那么返回值将被保存在对象的_result对象中;如果执行在过程中抛出一个异常,那么这个异常将被保存在_exception域中。我们要确保这个异常不会超出Catch块的范围,否则将在线程池上导致一个未处理异常,这将使进程崩溃。
可以看到,EndWork的实现里调用simpleResult.End()方法,而simpleResult.End的实现里首先判断是否完成了,如果没完成则调用内核对象阻塞线程。
public T End() { // Wait for the work to finish, if it hasn't already. if (_isCompleted == 0) { _asyncWaitHandle.WaitOne(); _asyncWaitHandle.Close(); } // Propagate any exceptions or return the result. if (_exception != null) throw _exception; return _result; }
所以正确的调用EndWork的时机应该是在回调方法里,而不应该直接调用。当然,在我们的实现里为了简单起见,直接实例化了一个ManualResetEvent类型的_asyncWaitHandle,因此就算在回调方法里调用了EndWork方法,仍然会创建一个内核对象。
_isCompleted = 1; _asyncWaitHandle.Set(); if (_callback != null) _callback(this);
更高效的实现是只有在直接调用了AsyncWaitHandle属性或者在isCompleted = 1之前调用了EndXXX方法时,才应该初始化_asyncWaitHandle。
.NET Framework中常用的一些APM
1.委托
所有的委托类型除了提供Invoke方法外,还要提供一个BeginInvoke方法。但是应尽量避免使用,异步委托的实现并不高效。
2.System.IO.Stream
基类Stream类中为BeginWrtie,BeginRead提供了一个默认的实现,因此所有它的子类都自动有相应的方法。大多数的Stream,比如经常用到的FileStream,都重写了这个默认的实现,依赖非托管的Windows Overlapped I/O获取更高的效率。
3.System.Net.WebRequest
该类并没有实现BeginGetResponse,BeginGetRequestStream这些方法,因此将抛出一个NotImplementedException,但它的子类如HttpWebRequest等,都提供了实现。
总结:
APM是用于更低层次框架和库组件,在这些环境中注重的是灵活性而不是完成事件的发生方式(当然,我们通常都是用的是回调的方式)。通常一般应用层开发人员并不需要关心细节和细粒度的控制,而是希望简单的能够在某个事件完成后调用一些方法,或者更方便的回到GUI线程,这时EAP是一种不错的模式。
下一篇介绍EAP和UI线程以及UI编程模式的相关知识,过渡到IOS开发了。