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.Wait
或Task
。 这将导致我在博客上完整解释的死锁 。
简而言之, await
将(默认情况下)捕获当前的“上下文”并使用它来恢复其async
方法。 在此示例中,“上下文”是WPF UI上下文。
因此,当您的代码对WhenAll
返回的任务进行WhenAll
,它会捕获WPF UI上下文。 稍后,当该任务完成时,它将尝试在UI线程上继续。 但是,如果UI线程被阻止(即,在对Wait
或Result
的调用中),则async
方法无法继续运行,并且永远不会完成它返回的任务。
正确的解决方案是使用await
而不是Wait
或Result
。 这意味着您的调用代码需要是async
,并且它将通过您的代码库传播。 最终,您需要决定如何使您的UI异步,这本身就是一门艺术。 至少首先,您需要一个async void
事件处理程序或某种异步MVVM命令(我在MSDN文章中探索异步MVVM命令 )。 从那里你需要设计一个适当的异步UI; 即,当异步操作正在进行时,UI的外观以及它允许的操作。