C#/ .NET 4.5 – 为什么“等待Task.WhenAny”在WPF应用程序的UI线程中提供Task.Delay时永远不会返回?

鉴于以下代码,为什么Task.WhenAny在提供1秒的Task.Delay时永远不会返回? 从技术上讲,我不确定它是否会在延长的时间后返回,但是在15秒左右之后我不会手动终止该过程。 根据文档,我不需要手动启动delayTask,事实上,如果我尝试手动启动,我会收到exception。

当用户选择WPF应用程序中的上下文菜单项时,将从UI线程调用代码,但如果我为上下文菜单项指定了click方法,则在新线程中运行此代码时,它可以正常工作。

public void ContextMenuItem_Click(object sender, RoutedEventArgs e) { ... SomeMethod(); ... } public void SomeMethod() { ... SomeOtherMethod(); .... } public void SomeOtherMethod() { ... TcpClient client = Connect().Result; ... } //In case you're wondering about the override below, these methods are in //different classes i've just simplified things here a bit so I'm not posting //pages worth of code. public override async Task Connect() { ... Task connectTask = tcpClient.ConnectAsync(URI.Host, URI.Port); Task delayTask = Task.Delay(1000); if (await Task.WhenAny(connectTask, delayTask) == connectTask) { Console.Write("Connected\n"); ... return tcpClient; } Console.Write("Timed out\n"); ... return null; } 

如果我将ContextMenuItem_Click更改为以下它可以正常工作

 public void ContextMenuItem_Click(object sender, RoutedEventArgs e) { ... new Thread(() => SomeMethod()).Start(); ... } 

我预测你的调用堆栈会更进一步,你正在调用Task.WaitTask.Result 。 这将导致我在博客上完整解释的死锁 。

简而言之, await将(默认情况下)捕获当前的“上下文”并使用它来恢复其async方法。 在此示例中,“上下文”是WPF UI上下文。

因此,当您的代码对WhenAll返回的任务进行WhenAll ,它会捕获WPF UI上下文。 稍后,当该任务完成时,它将尝试在UI线程上继续。 但是,如果UI线程被阻止(即,在对WaitResult的调用中),则async方法无法继续运行,并且永远不会完成它返回的任务。

正确的解决方案是使用await而不是WaitResult 。 这意味着您的调用代码需要是async ,并且它将通过您的代码库传播。 最终,您需要决定如何使您的UI异步,这本身就是一门艺术。 至少首先,您需要一个async void事件处理程序或某种异步MVVM命令(我在MSDN文章中探索异步MVVM命令 )。 从那里你需要设计一个适当的异步UI; 即,当异步操作正在进行时,UI的外观以及它允许​​的操作。