使用异步/等待任务时,Task.WaitAll 函数挂起不动。

7

我可能忽略了一些显而易见的东西,比如SynchronizationContext上的死锁,但我不知道为什么会发生这种情况,并且不知道如何避免它...

所以,这个应用程序是Azure WorkerRole(本质上,据我理解,是没有UI的常规Windows应用程序)。在应用程序中,我正在尝试并行执行多个任务,我的代码的示意版本如下:

private async Task DoJob()
{
    await SomeIoOperation();
}


public void MethodExecutedByWorkerRoleInAnInfiniteLoop()
{
    Log("Start");
    Task.WaitAll(DoJob(), DoJob(), DoJob());
    Log("End");
}

我的想法是,我们在这里使用默认的SynchronizationContext,因此我们应该避免死锁,就像在ASP.NET等类似情况下一样。

然而,有时候执行会挂起 - Start被记录,但End要等好几天才能看到,除非我重新启动worker role。自然地,DoJob不可能运行那么长时间。奇怪的是,这并不是在worker role启动后立即发生的 - 可能需要数天或数周的正常操作才会挂起。

我可能过于简化了代码 - 也许SomeIoOperation中发生的确切事情很重要 - 但我感觉这与SynchronizationContext的误用有关,显而易见。

SomeIoOperation.ConfigureAwait(false)有帮助吗?我甚至无法测试它,因为我不知道它是否有效,问题是否已经解决,或者是否还会在未来几天内挂起。

有什么想法吗?


1
本文将为您阐明原因 http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html - Gusdor
2
添加 Log(SynchronizationContext.Current);,如果它不是 null,那么你就会知道你在 SynchronizationContext 上有问题。 - i3arnon
DoJob将始终希望与调用上下文同步。 SomeIoOperation使用哪个上下文并不重要。 - Gusdor
@MichaelSagalovich,您知道什么阻止这些文章适用于此案吗? - Gusdor
@Gusdor,他们说的是关于UI应用程序,而我的不是UI。我认为这很重要,但我可能错了。 - Michael Sagalovich
显示剩余6条评论
1个回答

14

你在SynchronizationContext上遇到了死锁问题。

只需使用WhenAll而不是WaitAll

public async Task MethodExecutedByWorkerRoleInAnInfiniteLoop()
{
    Log("Start");
    await Task.WhenAll(DoJob(), DoJob(), DoJob());
    Log("End");
}

一切都会正常运行。


1
这个方法在循环中执行 - 调用者希望知道方法何时完成。在我看来,这个修订应该返回 Task - Gusdor
谢谢。我只是忘记在任务上更改void。 - Kirill Bestemyanov
但是在我的循环中,我将不得不执行 MethodExecutedByWorkerRoleInAnInfiniteLoop().Wait() - 这样不会导致死锁吗?据我所知,在 Microsoft.WindowsAzure.ServiceRuntime 中没有 RoleEntryPoint 的异步版本,因此我将无法在任何地方等待我的 MethodExecutedByWorkerRoleInAnInfiniteLoop - Michael Sagalovich
1
如果您想使用 await,则应将代码重构为 _async all the way_。 - Gusdor
@Gusdor,我知道这一点,并且正在研究使用https://github.com/StephenCleary/AsyncEx/wiki/AsyncContext来解决这个问题。不过,我真的很想知道为什么会出现死锁,而我认为不应该有任何死锁。 - Michael Sagalovich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接