基于事件模式的异步页

发布时间:2026/7/5 4:46:06
基于事件模式的异步页
如果您看过我的博客【C#客户端的异步操作】 那么对【基于事件模式的异步】这个词就不会再感到陌生了。在那篇博客中我就对这种异步模式做过介绍 只不是上次是在WinForm程序中演示的而已。为了方便对比我再次把那段代码贴出来/// summary /// 基于事件的异步模式 /// /summary /// param namestr/param private void CallViaEvent(string str) { MyAysncClientstring, string client new MyAysncClientstring, string(ServiceUrl); client.OnCallCompleted new MyAysncClientstring, string.CallCompletedEventHandler(client_OnCallCompleted); client.CallAysnc(str, str); } void client_OnCallCompleted(object sender, MyAysncClientstring, string.CallCompletedEventArgs e) { //bool flag txtOutput.InvokeRequired; // 注意这里flag的值是false也就是说可以直接操作UI界面 if( e.Error null ) ShowResult(string.Format({0} {1}, e.UserState, e.Result)); else ShowResult(string.Format({0} Error: {1}, e.UserState, e.Error.Message)); }上次我就解释过这种方法在WinForm中非常方便。幸运的是ASP.NET的异步页也支持这种方式。ASP.NET的异步页中的实现代码如下private void CallViaEvent(string str) { MyAysncClientstring, string client new MyAysncClientstring, string(ServiceUrl); client.OnCallCompleted new MyAysncClientstring, string.CallCompletedEventHandler(client_OnCallCompleted); client.CallAysnc(str, str); } void client_OnCallCompleted(object sender, MyAysncClientstring, string.CallCompletedEventArgs e) { Trace.Warn(client_OnCallCompleted ThreadId System.Threading.Thread.CurrentThread.ManagedThreadId.ToString()); if( e.Error null ) labMessage.Text string.Format({0} {1}, e.UserState, e.Result); else labMessage.Text string.Format({0} Error: {1}, e.UserState, e.Error.Message); }搞什么呀这二段代码是一样的嘛。您是不是也有这样的感觉呢仔细看这二段代码还是能发现它们有区别的。这里我就不指出它们了。它们与异步无关说出它们意义不大 反而我更希望您对【基于事件模式的异步】留个好印象它们就是一样的。再来看一下如何发出多个异步任务protected void button1_click(object sender, EventArgs e) { Trace.Write(button1_click ThreadId System.Threading.Thread.CurrentThread.ManagedThreadId.ToString()); string str textbox1.Text; // 注意这个异步任务我设置了2秒的超时。它应该是不能按时完成任务的。 MyAysncClientstring, string client new MyAysncClientstring, string(ServiceUrl, 2000); client.OnCallCompleted new MyAysncClientstring, string.CallCompletedEventHandler(client_OnCallCompleted); client.CallAysnc(str, str); // 开始第一个异步任务 string str2 T2_ Guid.NewGuid().ToString(); MyAysncClientstring, string client2 new MyAysncClientstring, string(ServiceUrl); client2.OnCallCompleted new MyAysncClientstring, string.CallCompletedEventHandler(client2_OnCallCompleted); client2.CallAysnc(str2, str2); // 开始第二个异步任务 } void client2_OnCallCompleted(object sender, MyAysncClientstring, string.CallCompletedEventArgs e) { ShowCallResult(2, e); // 再来一个异步调用 string str3 T3_ Guid.NewGuid().ToString(); MyAysncClientstring, string client3 new MyAysncClientstring, string(ServiceUrl); client3.OnCallCompleted new MyAysncClientstring, string.CallCompletedEventHandler(client3_OnCallCompleted); client3.CallAysnc(str3, str3); // 开始第三个异步任务 }页面的执行过程如下图这里要说明一下了在【C#客户端的异步操作】中我就给出这个类的实现代码 不过这次我给它增加了超时功能增加了一个重载的构造函数需要在构造函数的第二个参数传入。 今天我就不贴出那个类的代码了有兴趣的自己去下载代码阅读吧。 在上次贴的代码你应该可以发现在CallAysnc()时就已经开始了异步操作。对于本示例来说也就是在button1_click就已经开始了二个异步操作。这是个什么意思呢可以这样来理解前二个任务显然是和LoadCompletePreRender事件阶段的代码在并行执行的。有意思的是第三个任务是在第二个任务的结束事件中开始的但三个任务的结束操作全在页面的PreRender事件才得到处理。 下面我再把这个例子来改一下就更有趣了protected void button1_click(object sender, EventArgs e) { Trace.Write(button1_click ThreadId System.Threading.Thread.CurrentThread.ManagedThreadId.ToString()); string str textbox1.Text; // 注意这个异步任务我设置了2秒的超时。它应该是不能按时完成任务的。 MyAysncClientstring, string client new MyAysncClientstring, string(ServiceUrl, 2000); client.OnCallCompleted new MyAysncClientstring, string.CallCompletedEventHandler(client_OnCallCompleted); client.CallAysnc(str, str); // 开始第一个异步任务 System.Threading.Thread.Sleep(3000); string str2 T2_ Guid.NewGuid().ToString(); MyAysncClientstring, string client2 new MyAysncClientstring, string(ServiceUrl); client2.OnCallCompleted new MyAysncClientstring, string.CallCompletedEventHandler(client2_OnCallCompleted); client2.CallAysnc(str2, str2); // 开始第二个异步任务 }现在在第一个任务发出后我让线程等待了3秒也就是等到了第一个任务的超时。然后再开始第二个任务。也就是说在button1_click事件还没执行完毕第一个任务就结束了。现在您可以猜一下此时的执行过程是个什么样的。猜好了就来看下图吧。现在明白了吧哪怕是在PostBackEvent阶段就结束的任务也要等到PreRender之后才能得到处理。至于为什么会是这样的我以后再讲。今天只要记住本文的第一张图片就好了。我可是好不容易才找出这张图片来的且为了让您能看得更清楚还花了些时间修改了它。在那个图片后面我还说过在一个异步页的【页面生命周期】中所有异步任务在执行时所处的阶段。并在后面注明了这里的所有这个词也不太恰当。现在可以解释为什么不恰当了【基于事件模式的异步】的开始阶段并不一定要PreRender事件之后而对于前二种异步面的实现方式则是肯定在PreRender事件之后。至于这其中的原因同样您要等待我的后续博客了。回到顶部各种异步页的实现方式比较前面介绍了3种异步页的实现方式我打算在这里给它们做个总结及比较。当然这一切只代表我个人的观点仅供参考。为了能给出一个客观的评价我认为先有必要再给个示例把这些异步方式放在一起执行就好像把它们放在一起比赛一样 或许这样会更有意思同时也会让我给出的评价更有说服力。在下面的示例中我把上面说过的3种异步方式放在一起并让每种方法执行多次(共10个异步任务)实验代码如下protected void button1_click(object sender, EventArgs e) { ShowThreadInfo(button1_click); // 为PageAsyncTask设置超时时间 Page.AsyncTimeout new TimeSpan(0, 0, 7); // 开启4个PageAsyncTask其中第14个任务不接受并行执行23则允许并行执行 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) { MyHttpClientstring, string http new MyHttpClientstring, string(); http.UserData taskName; PageAsyncTask task new PageAsyncTask(BeginCall_Task, EndCall_Task, TimeoutCall_Task, http, executeInParallel); RegisterAsyncTask(task); } private void Async_AddOnPreRenderCompleteAsync(string taskName) { MyHttpClientstring, string http new MyHttpClientstring, string(); http.UserData taskName; AddOnPreRenderCompleteAsync(BeginCall, EndCall, http); } private void Async_Event(string taskName, int timeoutMilliseconds) { MyAysncClientstring, string client new MyAysncClientstring, string(ServiceUrl, timeoutMilliseconds); client.OnCallCompleted new MyAysncClientstring, 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个原因造成的1.在这个例子中MyAysncClient_1, MyAysncClient_2, MyAysncClient_3, AddOnPreRenderCompleteAsync_1 由于都是异步任务所以基本上是并行执行的2. 由于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秒它当然就不能在指定时间内正常完成。似乎到这里这些异步任务的执行过程都解释完了但是有二个很奇怪的现象您有没有发现1. 为什么AddOnPreRenderCompleteAsync的任务全执行完了之后才轮到PageAsyncTask的任务呢2. 还有前面说过的为什么是BeginCall AddOnPreRenderCompleteAsync_1最先显示呢这一切绝非偶然如果您有兴趣可下载我的示例代码你运行千遍万遍还将是这个结果。这些原因我以后再谈今天的博客只是想告诉您这样一个结果就行了。不过为了能让您能容易地理解后面的内容我暂且告诉您PageAsyncTask是建立在AddOnPreRenderCompleteAsync的基础上的。有了前面这些实验结果我们再来对这3种异步页方法做个总结及比较。1. AddOnPreRenderCompleteAsync: 它提供了最基本的异步页的使用方法。就好像HttpHandler一样它虽能处理请求但不太方便显得比较原始。 由于它提供的是比较原始的方法您也可以自行包装您的高级功能。2. PageAsyncTask: 与AddOnPreRenderCompleteAsync相比它增加了超时以及并行执行的功能但我也说过它是建立在AddOnPreRenderCompleteAsync的基础之上的。 如果把AddOnPreRenderCompleteAsync比作为HttpHandler那么PageAsyncTask则就像是Page 。因此它只是做了些高级的包装罢了。3. 基于事件的异步方式与前2者完全没有关系它只依赖于AspNetSynchronizationContext。这里有必要强调一下 【基于事件的异步方式】可以理解为一个设计模式也可以把它理解成对最基础的异步方式的高级包装。 它能提供或者完成的功能依赖于包装的方式及力度。 在我提供的这个包装类中它也可以实现与PageAsyncTask一样的并行执行以及超时功能。后二种方法功能强大的原因是来源于高级包装由于包装过程也会更复杂因此性能或许也会有微小的损失。 如果您不能接受这点性能损失可能还是选AddOnPreRenderCompleteAsync会比较合适。 不过我要再次提醒您它不支持并行执行不支持超时。请容忍我再夸一下【基于事件的异步模式】从我前面的示例代码尤其是与WinForm中的示例代码的比较中 我们可以清楚的发现这种方式是非常易用的。掌握了这种方式至少在这二大编程模型中都是适用的。 而且它能在异步页的执行周期中较早的进入异步等待状态因此能更快的结束执行过程。 想想【从Begin Raise PostBackEvent到End PreRender这中间还可以执行多少代码是不确定的】吧。【基于事件的异步模式】的优点不仅如此我的演示代码中还演示了另一种用法在一个完成事件中我还能再开启另一个异步任务。这个优点使我可以有选择性地启动后续的异步操作。但是这个特性是另2个不可能做到的 这个原因