异步方法阻塞在未等待的任务上

B. *_*all 1 .net c# asynchronous async-await

在我当前的项目中,我有一段代码,在将其简化为我遇到问题的地方后,它看起来像这样:

private async Task RunAsync(CancellationToken cancel)
{
    bool finished = false;
    while (!cancel.IsCancellationRequested && !finished)
        finished = await FakeTask();
}

private Task<bool> FakeTask()
{
    return Task.FromResult(false);
}
Run Code Online (Sandbox Code Playgroud)

如果我不等待就使用此代码,无论如何我最终都会阻塞:

// example 1
var task = RunAsync(cancel); // Code blocks here...
... // Other code that could run while RunAsync is doing its thing, but is forced to wait
await task;

// example 2
var task = RunAsync(cancelSource.Token); // Code blocks here...
cancelSource.Cancel(); // Never called
Run Code Online (Sandbox Code Playgroud)

在实际项目中,我实际上并没有使用 FakeTask,通常会有一些 Task.Delay 在那里等待,所以大部分时间代码实际上并没有阻塞,或者只是有限的迭代次数.

然而,在单元测试中,我使用了一个模拟对象,它几乎可以完成 FakeTask 所做的工作,所以当我想查看 RunAsync 是否以我期望的方式响应它的 CancellationToken 被取消时,我被卡住了。

我发现我可以通过await Task.Delay(1)在 RunAsync 的顶部添加例如来解决这个问题,以强制它真正异步运行,但这感觉有点麻烦。有更好的选择吗?

Jes*_*Wit 5

Task.FromResult实际上是同步运行的,就像await Task.Delay(0). 如果您想实际模拟异步代码,请调用Task.Yield(). 这将创建一个可等待任务,该任务在等待时异步返回到当前上下文。

  • @OlegI 不,没有。请参阅[此处](/sf/answers/1296905641/)或[此处](/sf/ask/2338490521/) (3认同)

Eri*_*ert 5

你对做什么有一个不正确的心理图景await。的意思await是:

  • 检查等待对象是否完整。如果是,则获取其结果并继续执行协程。
  • 如果它不完整,则将当前方法的剩余部分注册为 awaitable 的延续,并通过将控制权返回给调用者来暂停协程。(请注意,这使它成为一个semicoroutine。)

在您的程序中,“假”等待总是完整的,因此协程永远不会暂停。

有更好的选择吗?

如果您的控制流逻辑要求您暂停协程,则使用Task.Yield.