【问题标题】:ASP.NET async task execution sequenceASP.NET 异步任务执行序列
【发布时间】:2016-06-20 15:14:38
【问题描述】:

我刚刚在 asp.net 上遇到并发编码,发现有两种方法可以在 Page_Load 方法中触发异步方法

  1. RegisterAsyncTask(new PageAsyncTask(DoSthAsync()));
  2. 等待 DoSthAsync();

但是,它们有不同的操作结果。对于案例 1,RegisterAsyncTask 之后的代码将在 DoSthAsync() 中的任何代码之前立即运行。而 await 之后的代码将在 DoSthAsync() 完成时运行。

例如:

//protected async void Page_Load(object sender, EventArgs e)
protected void Page_Load(object sender, EventArgs e)
{    
    Response.Write("Start</br>");
    Response.Flush();
    RegisterAsyncTask(new PageAsyncTask(DoSthAsync()));
    //await DoSthAsync();
    Response.Write("End</br>");
    Response.Flush();
}

public async Task LoadSomeData()
{
    await Task.Delay(1000);
    Response.Write("Do Sth Async</br>");
    Response.Flush();
}

这段代码sn-p会产生如下结果:

Start
End
Do Sth Async  *(after 1 second delay)*

当我取消注释 await DoSthAsync() 代码并注释 RegisterAsyncTask 时,将显示以下结果。

Start
Do Sth Async  *(after 1 second delay)*
End

在 Rick Anderson 的文章 Using Asynchronous Methods in ASP.NET 4.5 中,他建议使用 RegisterAsyncTask 可以更好地控制代码执行。但是,这会产生一个我正在寻找的意外结果,而 page_load 中的 await 将生成相同的结果,因为我在 Windows 程序中尝试类似的代码序列。

在他的文章中,Rick 还提供了在 GetPWGsrvAsync() 触发之前启动秒表的代码,并在所有异步代码完成后停止显示代码执行了多长时间。它在屏幕截图中显示经过的时间为 0.872 秒。因此,在所有先前的代码(包括所有异步方法)完成后,预计秒表会停止。

.......
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
RegisterAsyncTask(new PageAsyncTask(DoSthAsync()));
//await DoSthAsync();
stopWatch.Stop();
Response.Write(String.Format("Elapsed time:{0}",stopWatch.Elapsed.Milliseconds / 1000.0));
Response.Write("</br>");
Response.Flush();
.......

虽然我按照与上面的代码 sn-p 相同的方式进行操作,但在 RegisterAsyncTaskawait DoSthAsync() 中得到不同的结果。虽然 经过时间 显示在不同的位置,但它们都给了我非常短的经过时间,大约 0.010s 或 10+ms 并且相信秒表在异步函数 DoSthAsync() 被解雇。其实窗口程序也有类似的结果,很快就停止了。

在对问题进行了长篇描述之后,我想问一下异步编码的哪种方式具有更好的控制和代码模式。在这两者之间,我怎么能期望秒表的结果给我准确的代码经过时间。

【问题讨论】:

    标签: c# asp.net concurrency async-await task-parallel-library


    【解决方案1】:

    页面异步任务早在async-await 之前就已经存在。

    页面异步任务是关于在请求生命周期中执行异步任务(不一定是Tasks)。

    async-await 是关于具有异步操作的异步方法。

    如果你在Page_Load中使用async-await,因为它是一个void-returning方法,运行时无法知道该方法是异步的,以及它的异步工作是否完成。

    查看the documentation for PageAsyncTask,看看最适合您的需求。但是你应该认真看看页面异步任务。

    【讨论】:

    • 我还建议您阅读以下 Scott Hanselmann 关于此主题的文章:hanselman.com/blog/…
    • @ManuelZelenka 我也读过这篇文章,但没有提到 RegisterAsyncTask 和 await 之间的区别。他刚刚提到“将 async 与 voids 一起使用并不稳定或不可靠。但是,您所要做的就是调用 Page.RegisterAyncTask - 这没有任何麻烦,您将处于一个更好更灵活的地方。”这与我提到的 Rick 的文章相同。而他的示例在 Page_Load 中只有一个动作。
    • 是的,我很久以前在 asp.net 2.0 时使用 RegisterAsyncTask。与新的 async-await 一起使用时感觉有点困难。代码执行的顺序是出乎意料的。 Rick 的文章提到“与 RegisterAsyncTask 挂钩的方法将在 PreRender 之后立即运行”。所以我上面写的示例意味着 DoSthAsync() 实际上并没有在 Page_Load 运行?
    【解决方案2】:

    首先要做的事情是:您的Stopwatch 在这两种情况下都会返回如此短的时间,因为您使用了错误的属性。 stopWatch.Elapsed.Milliseconds 只会返回测量间隔的最后一秒的毫秒数。你想要的是stopWatch.ElapsedMilliseconds,因为这将返回测量期间经过的毫秒总数。这将为您提供两种使用方法之间的巨大差异。

    await DoSthAsync(); 的执行时间会略高于一秒。这是因为await关键字的基本意思是:等到异步操作成功完成后才执行下面的代码。这可以保证至少在这种情况下,代码以同步方式运行,但长时间运行的操作的执行被安排到 ThreadPool 线程。这实际上可以满足您的要求,但仍然有在 void 返回方法中使用 async/await 的缺点,这不是非法的,但我会避免这样做。你可以阅读它的所有缺点here

    使用RegisterAsyncTaskPageAsyncTask 的第二种方法的执行时间将低于1 秒。这是因为它只是简单地注册要执行的任务并立即返回以处理下面的代码。由于您的代码是在Page_Load 事件中编写的,因此您的长时间运行操作的执行只会在PreRenderComplete 事件完成后自动开始。但是您可以使用Page.ExecuteRegisteredAsyncTasks(); 手动开始执行任务。但请注意:这也不会等待您的任务完成,而是提前开始执行。

    基本上你有两个选择来获得你想要的结果:

    1. 使用您的第一种方法并接受缺点。
    2. 重构您的代码,以便您可以通过使用包含 EventHandlers 的 PageAsyncTask 的另一个构造函数来使用第二种方法。您可以使用这些 EventHandler 来执行应该在异步任务完成后运行的代码。您可以在 here 找到这种方法的一个很好的示例。

    您将如何处理这些信息取决于您。请注意,我强烈建议您使用选项 2。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多