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

Mat*_*att 5 .net c# task delay

鉴于以下代码,为什么Task.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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

Ste*_*ary 12

我预测你的呼叫堆栈会更进一步,你正在呼叫Task.Wait或者Task<T>.Result.这将导致我在博客上完整解释的死锁.

简而言之,会发生的事情是await(默认情况下)捕获当前的"上下文"并使用它来恢复其async方法.在此示例中,"上下文"是WPF UI上下文.

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

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