我一直在测试WebForms中的异步,一次,我的问题不是关于如何做某事,而是关于已经有效的事情,是否有效。这是我的测试代码:
protected override void OnPreRender(EventArgs e)
{
Response.Write("OnPreRender<Br>");
}
protected override void OnPreRenderComplete(EventArgs e)
{
Response.Write("OnPreRenderComplete<Br>");
}
protected async override void OnLoadComplete(EventArgs e)
{
Response.Write("OnLoadComplete<br>");
var t1 = Task.Factory.StartNew(() => {
System.Threading.Thread.Sleep(2000);
return 1;
});
//This actually does run:
Response.Write((await t1).ToString());
}
所以我的任务暂停一点,然后写出结果。我的问题是 - 我不希望这个工作,因为控制已经从OnLoadComplete方法产生 - 我希望页面实际完成渲染并在我的任务返回之前返回到客户端。
实际输出是:
OnLoadComplete
OnPreRender
1OnPreRenderComplete
因此很明显,OnLoadComplete方法产生了控制权,以便OnPreRender可以运行,然后控制返回到OnLoadComplete。我的预期结果是“1”将永远不会打印,因为后续事件将触发,页面的线程将被杀死,或者在发送响应后将发生任务后写入。我想,鉴于上述情况,即使我延迟10秒,结果也完全相同也就不足为奇了。
我假设WebForm引擎中有一些连线确保在页面生命周期的下一阶段进行之前完成任何等待。有谁知道这是怎么发生的?我害怕在需要在其他事件之前完成的方法中使用async / await,因为担心延迟会太晚,但如果它在内部处理,那么我不会担心。
答案 0 :(得分:11)
对于ASP.NET,您应该只在.NET 4.5上使用async
方法。我会在最后解释原因。
我有an article on SynchronizationContext
有助于填补有关ASP.NET如何工作的空白。首先,请注意ASP.NET很久以前支持异步操作(.NET 2.0 IIRC)。您可以通过几种不同的方式注册异步操作,但是对于此描述,我们将重点关注SynchronizationContext.OperationStarted
。
ASP.NET为每个请求创建一个SynchronizationContext
,并且在完成所有已注册的操作(通过调用SynchronizationContext.OperationCompleted
)之前,它知道请求未完成。 Event-based asynchronous pattern components(例如BackgroundWorker
)会在SynchronizationContext
开始和完成时自动通知async void
。
同样,SynchronizationContext
方法(新task-based asynchronous pattern)会在OnLoadComplete
启动和完成时自动通知async void
。因此,当您将OperationStarted
覆盖为OperationCompleted
方法时,编译器会为您插入代码,该代码将在开头调用async
,并在完成时调用async
。
到目前为止一切顺利 - ASP.NET现在知道在请求的所有异步操作完成之前保持请求处于活动状态。即使没有线程处理请求,也是如此。
现在需要注意的是:.NET 4.5之前的ASP.NET将在请求级别处理此问题。在ASP.NET 4.5中,生命周期管道变得更加智能,因此它会延迟页面生命周期,直到异步操作完成。使用旧的ASP.NET,“预”处理程序将在管道中的那一点启动,但可能要到晚些时候才能完成。新的ASP.NET将延迟页面执行的其余部分,以确保{{1}}处理程序在生命周期中继续之前完成。
此外,ASP.NET 4.5将检测您是否使用了{{1}}处理程序,其中不应该存在错误。