各种异步页的实现方式比较
前面介绍了3种异步页的实现方式,我打算在这里给它们做个总结及比较。当然,这一切只代表我个人的观点,仅供参考。
为了能给出一个客观的评价,我认为先有必要再给个示例,把这些异步方式放在一起执行,就好像把它们放在一起比赛一样,或许这样会更有意思,同时也会让我给出的评价更有说服力。
在下面的示例中,我把上面说过的3种异步方式放在一起,并让每种方法执行多次(共10个异步任务),实验代码如下:
{
ShowThreadInfo("button1_click");
// 为PageAsyncTask设置超时时间
Page.AsyncTimeout = new TimeSpan(0, 0, 7);
// 开启4个PageAsyncTask,其中第1,4个任务不接受并行执行,2,3则允许并行执行
Async_RegisterAsyncTask("RegisterAsyncTask_1", false);
Async_RegisterAsyncTask("RegisterAsyncTask_2", true);
Async_RegisterAsyncTask("RegisterAsyncTask_3", true);
Async_RegisterAsyncTask("RegisterAsyncTask_4", false);
// 开启3个AddOnPreRenderCompleteAsync的任务
Async_AddOnPreRenderCompleteAsync("AddOnPreRenderCompleteAsync_1");
Async_AddOnPreRenderCompleteAsync("AddOnPreRenderCompleteAsync_2");
Async_AddOnPreRenderCompleteAsync("AddOnPreRenderCompleteAsync_3");
// 最后开启3个基于事件通知的异步任务,其中第2个任务由于设置了超时,将不能成功完成。
Async_Event("MyAysncClient_1", 0);
Async_Event("MyAysncClient_2", 2000);
Async_Event("MyAysncClient_3", 0);
}
private void Async_RegisterAsyncTask(string taskName, bool executeInParallel)
{
MyHttpClient<string, string> http = new MyHttpClient<string, string>();
http.UserData = taskName;
PageAsyncTask task = new PageAsyncTask(BeginCall_Task, EndCall_Task, TimeoutCall_Task, http, executeInParallel);
RegisterAsyncTask(task);
}
private void Async_AddOnPreRenderCompleteAsync(string taskName)
{
MyHttpClient<string, string> http = new MyHttpClient<string, string>();
http.UserData = taskName;
AddOnPreRenderCompleteAsync(BeginCall, EndCall, http);
}
private void Async_Event(string taskName, int timeoutMilliseconds)
{
MyAysncClient<string, string> client = new MyAysncClient<string, string>(ServiceUrl, timeoutMilliseconds);
client.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client_OnCallCompleted);
client.CallAysnc(taskName, taskName);
}
执行过程如下图:

不知您看到这个执行过程是否会想到为什么会是这个样子的。至于为什么会是这个样子的,这就涉及到ASP.NET的异步页的执行过程,这个过程比较复杂,我以后再谈。今天咱们就来根据这个图片来谈谈比较表面化的东西,谈一下这三种方式的差别。
从上面的代码以及执行过程,可以看到一个有趣的现象,我明明是先注册的4个PageAsyncTask 。可是呢,最先显示的却是【BeginCall AddOnPreRenderCompleteAsync_1】。我想我这里使用显示这个词也是比较恰当的,为什么呢?因为,我前面已经解释过了,基于事件的异步的任务应该是在button1_click事件处理器中先执行的,只是我没有让它们显示罢了。接下来的故事也很自然,由于我将"MyAysncClient_2"设置为2秒的超时,它最先完成,只是结果为超时罢了。紧接着,"MyAysncClient_1"和"MyAysncClient_3"也执行结束了。嗯,是的:3个事件的异步任务全执行完了。
说到这里我要另起一段了,以提醒您的注意。
有没有注意到,前面说到的3个事件的异步任务全执行完了。这个时候,其它的异步任务绝大部分还没有开始呢,它们3个咋就先执行完了呢?
有意思吧,其实何止3个,如果再来5个基于事件的异步任务,它们还是会先执行完成,不信的话,看下图:

或许举这个例子把基于事件的异步方式捧高了。这里我也要客观的解释一下原因了:
出现这个现象主要由2个原因造成的:
在这个例子中,"MyAysncClient_1", "MyAysncClient_2", "MyAysncClient_3", "AddOnPreRenderCompleteAsync_1" 由于都是异步任务,所以基本上是并行执行的,
由于3个基于事件的异步方式先执行的,因此它们先结束了。
接着来解释图片所反映的现象。当基于事件的异步任务全执行完成后," EndCall AddOnPreRenderCompleteAsync_1" 也被调用了。说明"AddOnPreRenderCompleteAsync_1"这个任务彻底地执行完了。接下来,"AddOnPreRenderCompleteAsync_2","AddOnPreRenderCompleteAsync_3"也依次执行完了。
我一开始用RegisterAsyncTask注册的4个异步任务呢?终于,在前面的所有异步任务全部执行完成后,才开始了这类任务的执行过程。首先执行的是"RegisterAsyncTask_1",这个好理解。接下来,"BeginCall RegisterAsyncTask_2", "BeginCall RegisterAsyncTask_3"被连续调用了,这也好理解吧,因为我当时创建异步任务时,指定它们是允许与其它任务并行执行的,因此它们是一起执行的。 3秒后,2个任务同时执行完了,最后启动了"RegisterAsyncTask_4",由于它不支持并行执行,所以,它排在最后,在没有任何悬念中,"TimeoutCall RegisterAsyncTask_4"被调用了。这么正常啊,我设置过Page.AsyncTimeout = new TimeSpan(0, 0, 7); 因此,前二批PageAsyncTask赶在超时前正常结束了,留给"RegisterAsyncTask_4"的执行时间只有1秒,它当然就不能在指定时间内正常完成。
似乎到这里,这些异步任务的执行过程都解释完了,但是,有二个很奇怪的现象您有没有发现:
为什么AddOnPreRenderCompleteAsync的任务全执行完了之后,才轮到PageAsyncTask的任务呢?
还有前面说过的,为什么是"BeginCall AddOnPreRenderCompleteAsync_1"最先显示呢?
这一切绝非偶然,如果您有兴趣,可下载我的示例代码,你运行千遍万遍还将是这个结果。
这些原因我以后再谈,今天的博客只是想告诉您这样一个结果就行了。
不过,为了能让您能容易地理解后面的内容,我暂且告诉您:PageAsyncTask是建立在AddOnPreRenderCompleteAsync的基础上的。
有了前面这些实验结果,我们再来对这3种异步页方法做个总结及比较。
AddOnPreRenderCompleteAsync: 它提供了最基本的异步页的使用方法。就好像HttpHandler一样,它虽能处理请求,但不太方便,显得比较原始。由于它提供的是比较原始的方法,您也可以自行包装您的高级功能。
PageAsyncTask: 与AddOnPreRenderCompleteAsync相比,它增加了超时以及并行执行的功能,但我也说过,它是建立在AddOnPreRenderCompleteAsync的基础之上的。如果把AddOnPreRenderCompleteAsync比作为HttpHandler,那么PageAsyncTask则就像是Page 。因此它只是做了些高级的包装罢了。
基于事件的异步方式:与前2者完全没有关系,它只依赖于AspNetSynchronizationContext。这里有必要强调一下:【基于事件的异步方式】可以理解为一个设计模式,也可以把它理解成对最基础的异步方式的高级包装。它能提供或者完成的功能,依赖于包装的方式及力度。在我提供的这个包装类中,它也可以实现与PageAsyncTask一样的并行执行以及超时功能。
后二种方法功能强大的原因是来源于高级包装,由于包装,过程也会更复杂,因此性能或许也会有微小的损失。如果您不能接受这点性能损失,可能还是选AddOnPreRenderCompleteAsync会比较合适。不过,我要再次提醒您:它不支持并行执行,不支持超时。
请容忍我再夸一下【基于事件的异步模式】,从我前面的示例代码,尤其是与WinForm中的示例代码的比较中,我们可以清楚的发现,这种方式是非常易用的。掌握了这种方式,至少在这二大编程模型中都是适用的。而且,它能在异步页的执行周期中,较早的进入异步等待状态,因此能更快的结束执行过程。想想【从"Begin Raise PostBackEvent"到"End PreRender"这中间还可以执行多少代码是不确定的】吧。
【基于事件的异步模式】的优点不仅如此,我的演示代码中还演示了另一种用法: 在一个完成事件中,我还能再开启另一个异步任务。 这个优点使我可以有选择性地启动后续的异步操作。但是,这个特性是另2个不可能做到的!这个原因可以简单地表达为:在PreRender事件后,调用AddOnPreRenderCompleteAsync会抛异常。