【问题标题】:ASP.NET MVC - Why are these async tasks running immediately?ASP.NET MVC - 为什么这些异步任务会立即运行?
【发布时间】:2023-04-11 05:42:01
【问题描述】:

我有一个 ASP.NET MVC 异步操作方法,如下所示:

public async Task<ActionResult> IndexAsync()
{
    var tasks = new List<Task<string>>
    {
        GetSomethingAsync("a"),
        GetSomethingAsync("b")
    };

    await Task.WhenAll(tasks);

    return View();
}

private async Task<string> GetSomethingAsync()
{
    var data = await _someService.GetSomethingAsync().ConfigureAwait(false);
    return data.SomeData;
}

现在,当我调试并跳过 tasks 变量创建时,任务会立即执行。换句话说,当我将鼠标悬停在await 行中的tasks 上时,他们会说“RanToCompletion”。

为什么?

据我了解,应该创建任务,但在被 await Task.WhenAll(tasks) 阻塞调用触发之前处于“WaitingForActivation”状态。

有人可以向我解释发生了什么吗?我以前写过这样的代码,它通常按预期工作,所以我想知道这是 ASP.NET 还是 ASP.NET MVC 异步控制器?

TIA。

编辑 如果我将代码更改为:

var tasks = new List<Task<string>>
{
   Task.Run(() => GetSomethingAsync("a")),
   Task.Run(() => GetSomethingAsync("b"))
};

该方法按预期运行(直到await 才执行任务)。

在运行异步任务之前,我通常从不需要这样做,这在 ASP.NET MVC 中是否需要?

【问题讨论】:

  • 我敢打赌`_someService.GetSomethingAsync()`是同步的...
  • ConfigureAwait(false) 导致等待在当前上下文之外启动任务
  • @AlexeiLevenkov 啊,你是对的。这是“假的”异步/等待。签名都是 async/await,但底层代码实际上是一个同步的 REST 调用。这就是为什么......因为没有返回实际的任务。 ://
  • @StenPetrov 是的,但这仅与捕获的上下文有关,与执行模式无关,对吧?应该没关系。

标签: c# asp.net-mvc async-await asynccontroller


【解决方案1】:

根据您的评论,您实际上没有任何真正的异步代码 - 所以任务确实会同步返回并处于完成状态。

使方法真正异步的最简单方法是await Task.Yield()。这对于单元测试或由于某种原因必须异步但不会消耗太多时间的方法来说很好。如果您需要运行缓慢(阻塞或只是 CPU 密集型)方法 - Task.Run 就像您在问题中所说的那样,是使任务在单独线程上运行的合理方法。

注意事项

  • 标记方法async 本身不会使其异步,await 本身不会创建任何线程。
  • 网络调用最好使用真正的异步方法。 ASP.Net 的线程拉取有限,阻塞调用的消耗线程将耗尽负载下的拉取,导致死锁,因为await'ing 方法将无法找到要运行的线程。
  • 使用ConfigureAwait(false) 并不能防止ASP.Net 中基于负载的死锁,并且可以方便地释放HttpContext.Current 和线程的CultureInfo - 在ASP.Net 中使用它时要小心,尤其是这样做基本上没有好处(与 WPF/WinForm 跨线程调用相比,在新线程上恢复上下文的成本非常低)。

【讨论】:

    【解决方案2】:

    现在,当我调试并跳过任务变量创建时,任务会立即执行。换句话说,当我将鼠标悬停在等待行中的任务上时,它们会说“RanToCompletion”。

    我建议你阅读我的async intro。引用:

    异步方法的开头与任何其他方法一样执行。也就是说,它会同步运行,直到遇到“等待”(或引发异常)。

    因此,如果您有返回已完成任务的存根异步方法,那么您的方法调用(例如,GetSomethingAsync("a"))将同步完成。该任务在添加到列表时已经完成。

    据我了解,应该创建任务,但在被 await Task.WhenAll(tasks) 阻塞调用触发之前处于“WaitingForActivation”状态。

    WaitingForActivation 是一个不幸的名字。 For Promise Tasks, the WaitingForActivation state means it's actually already in progress.

    Task.WhenAllawait 不需要“触发”。 Task.Run(() =&gt; GetSomethingAsync("a")) 创建的任务已经在进行

    这可以通过在await Task.WhenAll 之前插入await Task.Delay(1000); 并在延迟之后检查任务的状态来在调试器中观察到。

    这在 ASP.NET MVC 中是否需要?

    没有。事实上,you should avoid Task.Run on ASP.NET。引用我关于异步 ASP.NET 的 MSDN 文章:

    您可以通过等待 Task.Run 来启动一些后台工作,但这样做没有任何意义。事实上,这实际上会干扰 ASP.NET 线程池试探法,从而损害您的可伸缩性。如果您在 ASP.NET 上有 CPU 密集型工作要做,最好的办法是直接在请求线程上执行它。作为一般规则,不要将工作排队到 ASP.NET 上的线程池中。

    【讨论】:

      猜你喜欢
      • 2022-11-16
      • 2014-12-01
      • 2020-09-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多