基于事件模式的异步页
如果您看过我的博客【C#客户端的异步操作】,那么对【基于事件模式的异步】这个词就不会再感到陌生了。在那篇博客中,我就对这种异步模式做过介绍,只不是,上次是在WinForm程序中演示的而已。为了方便对比,我再次把那段代码贴出来:
/// 基于事件的异步模式
/// </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的异步页中的实现代码如下:
{
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);
}
搞什么呀,这二段代码是一样的嘛。 您是不是也有这样的感觉呢?
仔细看这二段代码,还是能发现它们有区别的。这里我就不指出它们了。它们与异步无关,说出它们意义不大,反而,我更希望您对【基于事件模式的异步】留个好印象:它们就是一样的。
再来看一下如何发出多个异步任务:
{
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事件才得到处理。下面我再把这个例子来改一下,就更有趣了:
{
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事件之后。
至于这其中的原因,同样,您要等待我的后续博客了。