.net中提供的APM编程模型主要为了实现两个需求:
1. 非阻塞模型。
例如一个UI客户端要运行一个复杂算法得到一个结果,这时候UI最好是不能卡住的,所以就需要使用APM模式。
2. 节约线程资源
在使用同步API来访问网络的时候,在结果返回之前,无论是UI线程还是线程池线程,客户端会有一个UI线程在等待。此线程浪费了内存资源,所以最好应该避免这么做,这时候也需要使用APM编程模式了。
大部分情况下,都不需要自己来定义AsyncResult。可以直接使用系统提供的API来得到一个实现了IasyncResult的对象。任何委托对象都支持APM模式,如下:
Func<string>f = new Func<string>(()=> "1");
f.BeginInvoke(new AsyncCallback(asyncRet=>
{
strings = f.EndInvoke(asyncRet);
Console.WriteLine(s);
}), null);
使用BeginInvoke发起调用,使用EndInvoke方法得到结果,Callback委托大部分情况下都不会在调用BeginInvoke的线程上被调用。
很多访问网络和文件的类也提供了支持APM的API,这些API都是以Begin和End开头。使用这些API,就可以得到很高的性能,因为这些API不会阻塞你的线程,也不会占用任何线程用来等待,线程只会去做有用的工作,而不会用于等待。
什么时候需要自己来实现APM模式呢?考虑如下情况:
有10个客户端每隔一秒钟就去服务端查询有没有属于自己的事件发生。如果没有事件就返回。这时候会发生什么状况?服务端的压力很变的很大,每秒钟会有10个请求发生,假如有1000个客户端,而服务端又什么事件都没有,那么每秒钟服务端要处理1000个请求。而这1000个请求没有返回任何有意义的内容。
一个改进的策略是,每次客户端查询的时候,服务端发现没有任何事件属于该客户端,那么就让此请求等待10秒钟,如果10秒内有事件发生,那么离开返回,如果到了第10秒,还没有事件发生,那么就返回一个空列表。在这种方案下,假如10秒内没有任何事件发生,那么每10秒内会有1000个请求。显然这种方案更好一些,但是请考虑,如果服务端采用同步模型,在每个请求所等待的10秒内,都会占用一个线程,那么在10秒内会有1000个线程在等待。按照1个线程占用1mb内存计算,1000个客户端轮询要至少占用1G内存。而这些占用内存的线程所做的事情只有等待。这显然是不合理的。
所以一个更好的方案是,在这1000个请求等待的10秒内,不要有任何线程在等待。这正是APM模式能够完成的,只是我们需要自定义一个实现了IAsyncResult的类。
public class AsyncResult: IAsyncResult, IDisposable
{
privateconst intStatePending = 0;
privateconst intStateCompletedSynchronously = 1;
privateconst intStateCompletedAsynchronously = 2;
privatereadonly AsyncCallback_asyncCallback;
privatereadonly object_asyncState;
privateint _completedState = StatePending;
privateManualResetEvent _asyncWaitHandle;
privateException _exception;
privateobject _result;
// asyncCallback和state都是APM模式需要的,我们需要保存它们。
publicAsyncResult(AsyncCallback asyncCallback, object state)
{
_asyncCallback =asyncCallback;
_asyncState = state;
}
// 当任务完成的时候调用此方法,在我们的例子中,有三种情况需要调用此方法。
1. 当获取事件的过程中出现异常的时候。
2. 当在10秒内有事件发生。此时需要将事件内容返回给客户端。
3. 当第10秒还没有事件发生,将返回客户端一个空列表。
当客户端发起查询时已经有事件存在,那么就可以直接在调用者线程完成任务并调用此方法,然后为completedSynchronously参数传递true。此参数告诉调用者任务在调用者线程完成。
当为exception参数传递了非空值的时候,忽略result参数。
public void Complete(objectresult, Exception exception, bool completedSynchronously)
{
intprevState = Interlocked.Exchange(ref _completedState,
completedSynchronously? StateCompletedSynchronously : StateCompletedAsynchronously);
//对于同一个AsyncResult对象,此方法只能被调用一次。
if(prevState != StatePending)
{
throw new InvalidOperationException("You can call Complete only once.");
}
_result = result;
_exception = exception;
//If the event exists, set it
if(_asyncWaitHandle != null)
{
_asyncWaitHandle.Set();
}
//If a callback method was set, call it
if(_asyncCallback != null)
{
_asyncCallback(this);
}
}
//此方法用于让APM的使用者得到返回值。如果调用此方法的时候任务还未完成,那么将会阻塞当前线程,直到调用返回为止。大部分情况下都应该在callback委托里面调用APM中的End方法,以避免线程阻塞。
publicvirtual objectEndInvoke()
{
//This method assumes that only 1 thread calls EndInvoke for this object
if(!IsCompleted)
{
// If the operation isn't done, wait for it
AsyncWaitHandle.WaitOne();
AsyncWaitHandle.Close();
_asyncWaitHandle = null;
}
//Operation is done: if an exception occured, throw it
if(_exception != null)
{
throw _exception;
}
return_result;
}
#region Implementation of IAsyncResult
publicobject AsyncState
{
get{ return _asyncState; }
}
publicbool CompletedSynchronously
{
get{ return Thread.VolatileRead(ref _completedState) == StateCompletedSynchronously;}
}
//大部分情况下都不应该访问此属性。创建内核对象会影响性能。
publicWaitHandle AsyncWaitHandle
{
get
{
if (_asyncWaitHandle == null)
{
bool done = IsCompleted;
ManualResetEvent evt = newManualResetEvent(done);
if (Interlocked.CompareExchange(ref _asyncWaitHandle, evt, null)!= null)
{
// Another thread created this object's event; dispose theevent we just created
evt.Close();
}
else
{
if (!done && IsCompleted)
{
// If the operation wasn't done when we created the eventbut now it is done, set the event
_asyncWaitHandle.Set();
}
}
}
return _asyncWaitHandle;
}
}
publicbool IsCompleted
{
get
{
return Thread.VolatileRead(ref _completedState) != StatePending;
}
}
#endregion
//如果没有在任务完成之前调用End方法,以及访问AsyncWaitHandle属性,就不需要调用Dispose。
publicvoid Dispose()
{
if(_asyncWaitHandle != null)
{
_asyncWaitHandle.Close();
}
}
}
以上实现其实是copy自msdn的某blog,做了一些轻微的改动。
使用AsyncResult:
1. 按照APM模式定义自己的Begin,End方法对。
2. 在Begin方法内部,构造AsyncResult对象,并返回。
3. 开始自己的任务,任务应该持有AsyncResult对象。
4. 当任务完成后,调用AsyncResult对象的Complete方法。