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();
    ...
}

C#/.NET 4.5-为什么“;等待任务.WhenAny“;当提供Task时永远不会返回.WPF应用程序中的延迟

我预测,在调用堆栈的更上层,您将调用Task.WaitTask<T>.Result。这将导致僵局,我在博客上对此进行了详细解释。

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

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

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