news 2026/7/5 4:45:41

基于事件模式的异步页

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于事件模式的异步页

如果您看过我的博客【C#客户端的异步操作】, 那么对【基于事件模式的异步】这个词就不会再感到陌生了。在那篇博客中,我就对这种异步模式做过介绍, 只不是,上次是在WinForm程序中演示的而已。为了方便对比,我再次把那段代码贴出来:

/// <summary> /// 基于事件的异步模式 /// </summary> /// <param name="str"></param> private void CallViaEvent(string str) { MyAysncClient<string, string> client = new MyAysncClient<string, string>(ServiceUrl); client.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client_OnCallCompleted); client.CallAysnc(str, str); } void client_OnCallCompleted(object sender, MyAysncClient<string, 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) { MyAysncClient<string, string> client = new MyAysncClient<string, string>(ServiceUrl); client.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client_OnCallCompleted); client.CallAysnc(str, str); } void client_OnCallCompleted(object sender, MyAysncClient<string, 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秒的超时。它应该是不能按时完成任务的。 MyAysncClient<string, string> client = new MyAysncClient<string, string>(ServiceUrl, 2000); client.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client_OnCallCompleted); client.CallAysnc(str, str); // 开始第一个异步任务 string str2 = "T2_" + Guid.NewGuid().ToString(); MyAysncClient<string, string> client2 = new MyAysncClient<string, string>(ServiceUrl); client2.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client2_OnCallCompleted); client2.CallAysnc(str2, str2); // 开始第二个异步任务 } void client2_OnCallCompleted(object sender, MyAysncClient<string, string>.CallCompletedEventArgs e) { ShowCallResult(2, e); // 再来一个异步调用 string str3 = "T3_" + Guid.NewGuid().ToString(); MyAysncClient<string, string> client3 = new MyAysncClient<string, string>(ServiceUrl); client3.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client3_OnCallCompleted); client3.CallAysnc(str3, str3); // 开始第三个异步任务 }

页面的执行过程如下图:

这里要说明一下了:在【C#客户端的异步操作】中我就给出这个类的实现代码, 不过,这次我给它增加了超时功能,增加了一个重载的构造函数,需要在构造函数的第二个参数传入。 今天我就不贴出那个类的代码了,有兴趣的自己去下载代码阅读吧。 在上次贴的代码,你应该可以发现,在CallAysnc()时,就已经开始了异步操作。对于本示例来说,也就是在button1_click就已经开始了二个异步操作。

这是个什么意思呢?
可以这样来理解:前二个任务显然是和LoadComplete,PreRender事件阶段的代码在并行执行的。
有意思的是:第三个任务是在第二个任务的结束事件中开始的,但三个任务的结束操作全在页面的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秒的超时。它应该是不能按时完成任务的。 MyAysncClient<string, string> client = new MyAysncClient<string, string>(ServiceUrl, 2000); client.OnCallCompleted += new MyAysncClient<string, string>.CallCompletedEventHandler(client_OnCallCompleted); client.CallAysnc(str, str); // 开始第一个异步任务 System.Threading.Thread.Sleep(3000); string str2 = "T2_" + Guid.NewGuid().ToString(); MyAysncClient<string, string> client2 = new MyAysncClient<string, string>(ServiceUrl); client2.OnCallCompleted += new MyAysncClient<string, 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,其中第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个原因造成的:
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个不可能做到的! 这个原因

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/5 4:44:28

【五行系统论:从生成回归到命理结构的综合阐释】

【五行系统论&#xff1a;从生成回归到命理结构的综合阐释】点击链接打开&#x1f449; https://m.toutiao.com/is/54BQC5u8Sho/ 54BQC5u8Sho igT:/ GI.iC :5pm复制此条消息&#xff0c;打开「今日头条APP」或「今日头条极速版APP」后直接查看~

作者头像 李华
网站建设 2026/7/5 4:41:25

Jenkins Blue Ocean 完全指南:现代化 CI/CD 可视化的终极利器

Jenkins Blue Ocean 完全指南&#xff1a;现代化 CI/CD 可视化的终极利器一、什么是 Blue Ocean&#xff1f;1.1 &#x1f7e2; 核心定义1.2 &#x1f535; 为什么需要 Blue Ocean&#xff1f;二、Blue Ocean 五大核心功能2.1 &#x1f7e1; 流水线可视化&#xff1a;一图胜千言…

作者头像 李华
网站建设 2026/7/5 4:41:20

委托以及延伸

先写个最简单的委托的用法 静态的和非静态方法的 结果会输出 您好 wlf Hello wlf 这是最原始的委托 2. 进化为匿名方法 声明完委托后 还要声明方法 是不是很麻烦 如果不声明方法 用匿名方法 可以帮我们 看 代码减少了很多吧~ 3.再进化为拉姆达表达式 上面的虽然简单了…

作者头像 李华
网站建设 2026/7/5 4:41:14

3分钟掌握Boss-Key老板键:一键隐藏窗口的终极隐私保护方案

3分钟掌握Boss-Key老板键&#xff1a;一键隐藏窗口的终极隐私保护方案 【免费下载链接】Boss-Key 老板来了&#xff1f;快用Boss-Key老板键一键隐藏静音当前窗口&#xff01;上班摸鱼必备神器 项目地址: https://gitcode.com/gh_mirrors/bo/Boss-Key 在快节奏的职场环境…

作者头像 李华
网站建设 2026/7/5 4:40:21

2026企业级大文件传输加速:专业解决方案引领未来趋势

随着数字化转型的不断推进&#xff0c;企业对高效、安全且稳定的大文件传输解决方案的需求达到了前所未有的高度。面对日益增长的数据量和更加复杂的业务场景&#xff0c;如何确保文件传输的速度、稳定性和安全性成为了众多企业面临的挑战。在此背景下&#xff0c;北京直真科技…

作者头像 李华