【问题标题】:Async method blocking on unawaited task异步方法阻塞未等待的任务
【发布时间】:2023-03-22 08:42:02
【问题描述】:

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

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

private Task<bool> FakeTask()
{
    return Task.FromResult(false);
}

如果我不等待就使用此代码,我最终还是会阻塞:

// 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

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

然而,在单元测试中,我使用了一个模拟对象,它几乎可以做 FakeTask 所做的事情,所以当我想查看 RunAsync 是否响应它的 CancellationToken 被取消时,我被卡住了.

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

【问题讨论】:

  • “类似这样的东西”。像这样足够您已经执行了您向我们展示的代码并遇到了同样的问题吗?
  • 如果你没有任何异步,你将同步运行。阅读blog.slaks.net/2014-12-23/parallelism-async-threading-explained
  • 只有当你有其他线程在运行时,异步才会做一些事情。如果你只有一个线程,那么它看起来就像阻塞一样。
  • @jdweng 如果这是真的,async 关键字在语言中将毫无用处。您所描述的是不是异步工作。
  • 假设cancel 是一个CancellationToken,这段代码甚至不会编译。

标签: c# .net asynchronous async-await


【解决方案1】:

你对await 所做的事情有一个错误的印象。 await的含义是:

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

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

有更好的选择吗?

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • await Task.Delay(0) 不是同步的,它异步等待
    • @OlegI 不,它没有。见herehere
    • 谢谢!似乎它不是异步的,只是因为延迟太短(0)
    • 谢谢。 await Task.Yield() 看起来确实比我的 await Task.Delay(1) 修复要好得多,而且没有引入不必要的延迟。
    【解决方案3】:

    正如@SLaks 所说,您的代码将同步运行。一件事是运行异步代码,另一件事是运行并行代码。

    如果您需要并行运行代码,您可以使用Task.Run

    class Program
    {
        static async Task Main(string[] args)
        {
            var tcs = new CancellationTokenSource();
            var task = Task.Run(() => RunAsync("1", tcs.Token));
            var task2 = Task.Run(() => RunAsync("2", tcs.Token));
            await Task.Delay(1000);
            tcs.Cancel();
            Console.ReadLine();
        }
    
        private static async Task RunAsync(string source, CancellationToken cancel)
        {
            bool finished = false;
            while (!cancel.IsCancellationRequested && !finished)
                finished = await FakeTask(source);
        }
    
        private static Task<bool> FakeTask(string source)
        {
            Console.WriteLine(source);
            return Task.FromResult(false);
        }
    }
    

    【讨论】:

      【解决方案4】:

      C# 的异步方法同步执行,直到它们必须等待结果为止。

      在您的示例中,没有方法必须等待结果,因此循环会一直运行,从而阻塞调用者。

      插入 await Task.Yield() 来模拟一些真正的异步工作应该会有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-06-04
        • 2015-12-04
        • 2022-01-04
        • 1970-01-01
        • 2016-03-13
        • 1970-01-01
        • 1970-01-01
        • 2011-04-03
        相关资源
        最近更新 更多