ASP.NET MVC - 为什么这些异步任务 运行 立即生效?

ASP.NET MVC - Why are these async tasks running immediately?

我有一个 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".

为什么?

根据我的理解,应该创建任务,但处于 "WaitingForActivation" 状态,直到被 await Task.WhenAll(tasks) 阻塞调用触发。

谁能给我解释一下这是怎么回事?我以前写过这样的代码,它通常按预期工作,所以我想知道这是 ASP.NET 还是 ASP.NET MVC 异步控制器的事情?

TIA。

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

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

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

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

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

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

备注

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

Now, when i debug and step over the tasks variable creation, the tasks are executed immediately. In other words, when i hover over tasks in the await line, they say "RanToCompletion".

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

The beginning of an async method is executed just like any other method. That is, it runs synchronously until it hits an “await” (or throws an exception).

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

From my understanding, the tasks should be created, but be in the "WaitingForActivation" state until triggered by the await Task.WhenAll(tasks) blocking call.

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

没有 "triggering" 需要由 Task.WhenAllawait 发生。 Task.Run(() => GetSomethingAsync("a")) 创建的任务 已经开始

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

is this needed in ASP.NET MVC?

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

You can kick off some background work by awaiting Task.Run, but there’s no point in doing so. In fact, that will actually hurt your scalability by interfering with the ASP.NET thread pool heuristics. If you have CPU-bound work to do on ASP.NET, your best bet is to just execute it directly on the request thread. As a general rule, don’t queue work to the thread pool on ASP.NET.