5

我一直在 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,因为担心继续为时已晚,但如果它是内部处理的,那我就不用担心了。

4

1 回答 1

11

对于 ASP.NET,您应该只使用async.NET 4.5 上的方法。我会在最后解释为什么。

我有一篇文章SynchronizationContext有​​助于填补关于它如何在 ASP.NET 上工作的空白。首先,请注意 ASP.NET 很久以前就支持异步操作(.NET 2.0 IIRC)。您可以通过几种不同的方式注册异步操作,但对于此描述,我们将重点关注SynchronizationContext.OperationStarted.

ASP.NET 为每个请求创建一个SynchronizationContext,并且它知道在所有注册的操作都完成之前(通过调用SynchronizationContext.OperationCompleted)请求才完成。基于事件的异步模式组件(例如)将在它们启动和完成时自动BackgroundWorker通知。SynchronizationContext

同样,async void方法(新的基于任务的异步模式SynchronizationContext)将在它们开始和完成时自动通知。OnLoadComplete因此,当您作为方法重写时async void,编译器会为您插入代码,这些代码将OperationStarted在开始OperationCompleted时和完成时调用。

到目前为止一切顺利 - ASP.NET 现在知道要保持请求处于活动状态,直到该请求的所有异步操作都完成为止。即使没有线程处理请求也是如此。

现在需要注意的是:.NET 4.5 之前的 ASP.NET 将在请求级别处理此问题。在 ASP.NET 4.5 中,生命周期管道变得更加智能,因此它会延迟页面生命周期,直到异步操作完成。对于旧的 ASP.NET,“pre”处理程序将在管道中的该点开始,但可能要到稍后才会完成。新的 ASP.NET 将延迟页面执行的其余部分,以确保async处理程序在生命周期中继续之前完成。

此外,ASP.NET 4.5 将检测您是否使用了async不应该使用的处理程序,并将通知您错误。

于 2013-02-07T17:37:29.167 回答