C#/.NET 4.5-为什么“;等待任务.WhenAny“;当提供Task时永远不会返回.WPF应用程序中的延迟
本文关键字:返回 WPF 永远 应用程序 延迟 时永 为什么 NET 等待 任务 WhenAny | 更新日期: 2023-09-27 18:06:13
给定以下代码,为什么ask.WhenAny
在提供1秒的Task.Delay
时永远不会返回?从技术上讲,我不确定它是否会在长时间后返回,但在15秒左右之后就不会了,之后我手动终止了这个过程。根据文档,我不应该被要求手动启动delayTask
,事实上,如果我尝试手动启动,我会收到一个异常。
当用户在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<TcpClient> 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<T>.Result
。这将导致僵局,我在博客上对此进行了详细解释。
简而言之,await
将(默认情况下(捕获当前的"上下文",并使用它来恢复其async
方法。在本例中,"上下文"是WPF UI上下文。
因此,当您的代码对WhenAll
返回的任务执行await
时,它会捕获WPF UI上下文。稍后,当该任务完成时,它将尝试在UI线程上恢复。但是,如果UI线程被阻塞(即,在对Wait
或Result
的调用中(,则async
方法将无法继续运行,并且永远不会完成它返回的任务。
正确的解决方案是使用await
而不是Wait
或Result
。这意味着您的调用代码需要是async
,并且它将通过您的代码库传播。最终,您需要决定如何使UI异步,这本身就是一门艺术。至少一开始,您需要一个async void
事件处理程序或某种异步MVVM命令(我在MSDN的一篇文章中探讨了异步MVVM指令(。从那里你需要设计一个合适的异步UI;即,当异步操作正在进行时,UI的外观以及它允许的操作。