WPF 调用异步方法的结果导致阻塞

Jua*_*DYB 0 c# wpf prism async-await

我有一个使用 Prism 的 WPF 应用程序,并且我发现异步方法存在额外行为。

我有一个带有这样的异步方法的类

public class ConfigurationManager(){
    public async Task<IList<T>> LoadConfigurationAsync<T>(){
        Tuple<IList<T>, bool> tuple = await LoadConfigurationItemsAsync();
        return tuple.Item1;
    }

    private async Task<Tuple<IList<T>, bool>> LoadConfigurationItemsAsync<T>(){
        await Task.Run(()  => 
        {

        });
        return new Tuple<IList<T>, bool>(configList.list, succes);
    }
}
Run Code Online (Sandbox Code Playgroud)

我需要以同步形式调用它们,因为我需要 ViewModelManager 的构造函数中的结果,并且我尝试使用 Result,因为这是以同步方式获取结果的方法之一。

public class ViewModelManager{
    private readonly ConfigurationManager _configManager;

    private void LoadConfiguration(){
        var config = _configManager.LoadConfigurationAsync().Result;
    }
}
Run Code Online (Sandbox Code Playgroud)

令我惊讶的是,这导致应用程序在 Result 调用中被阻止,我知道 Result 被阻止,但并非总是如此,方法的返回行永远不会被执行。我尝试使用它来调用它Task.Run并且它有效

private void LoadConfiguration(){
    var config = Task.Run(() => _configManager.LoadConfigurationAsync()).Result;
}
Run Code Online (Sandbox Code Playgroud)

我不知道这里发生了什么,也不知道为什么调用结果会导致应用程序被阻止以及为什么使用Task.Run它有效。这就像调用两个任务,因为该方法已经返回一个Task.

Mar*_*ell 6

访问.Result总是一个错误(有一个关于“我已经检查它已经完成”的小警告,还有关于价值任务和单一访问的次要警告;老实说:“总是”是比了解警告更有用的建议! )。

在最好的情况下,您已经实现了“同步优于异步”并无缘无故地占用了一个线程,从而影响了可扩展性,并可能影响了线程池。

但是,如果存在同步上下文,您可能会发现死锁。同步上下文充当工作管理器,在 WPF 的情况下:意味着默认情况下await通过 UI 线程汇集回调和s。所以想象一下:

  • 你的 UI 线程运行,在后台启动一些东西,然后进入.Result等待- 尚未返回到主应用程序循环
  • await您的后台事物完成,并尝试将待处理的操作发出信号为完成,即触发与该操作关联的延续
  • 逻辑await说“哦,我看到了一个同步上下文 - 我需要通过它来推动工作”,并向主应用程序循环添加一些内容
    • 添加到应用程序循环中的东西是告诉任务它已完成的东西,以便.Result可以完成
  • 繁荣:僵局

正是因为 UI 线程卡在 上.Result,所以应用程序循环永远不会真正将 标记.Result为已完成。有一些方法可以改进这一点.ConfigureAwait(false),但其主要目的只是为了避免当您希望它们在后台运行时在 UI 线程上运行它们;这种方法不应该被用来防止死锁,即使这恰好是一个巧合的副作用。

这里的“修复”很简单:不要使用.Result(或.Wait())。你需要await它,并采取相应的行动。