尝试同步调用 Async 方法。它永远等待 Task.Result

wwa*_*rby 3 .net c# asynchronous task-parallel-library async-await

因此,我正在编写一个应用程序,我想在其中公开一系列具有同步和异步等效方法的方法。为此,我认为最简单的方法是在 asnyc 方法中编写逻辑,并编写同步方法作为异步方法的包装器,同步等待它们传递结果。代码没有发挥作用。在下面的代码示例中(不是我的真实代码,而是基本问题的简化),该行Console.WriteLine(result)永远不会到达 - 前面的行永远挂起。但奇怪的是,如果我或多或少地将这个模式逐字复制到控制台应用程序中,它就会起作用。

我究竟做错了什么?这只是一个糟糕的模式吗?如果是这样,我应该使用什么模式?

public partial class MainWindow : Window {

    public MainWindow() {
        this.InitializeComponent();
        var result = MyMethod(); //Never returns
        Console.WriteLine(result);
    }

    public string MyMethod() {
        return MyMethodAsync().Result; //Hangs here
    }

    public async Task<string> MyMethodAsync() { //Imagine the logic here is more complex
        using (var cl = new HttpClient()) {
            return await cl.GetStringAsync("http://www.google.co.uk/");
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

Yuv*_*kov 5

这是一个经典的僵局。UI 正在等待异步方法完成,但异步方法尝试更新 UI 线程并BOOM,死锁。

但奇怪的是,如果我或多或少地将这个模式逐字复制到控制台应用程序中,它就会起作用。

那是因为您的 WinForm 应用程序有一个自定义的SynchronizationContext. 它是隐式捕获的,它的工作是在从await.

您真的应该公开异步操作的同步包装器吗?, 答案是

有一个解决办法,但我不太喜欢它。如果您绝对必须(您不)同步调用您的代码(同样,您确实不应该),请ConfigureAwait(false)在异步方法内部使用。这指示awaitable不要捕获当前同步上下文,因此它不会将工作重新编组到 UI 线程上:

public async Task<string> MyMethodAsync() 
{ 
    using (var cl = new HttpClient()) 
    {
        return await cl.GetStringAsync("http://www.google.co.uk/")
                       .ConfigureAwait(false);
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果您执行此操作,然后尝试调用任何 UI 元素,您最终会得到一个,InvalidOperationException因为您不会位于 UI 线程上。

通过构造函数初始化 UI 是一种常见模式。Stephan Cleary 有一个非常好的关于异步的系列,您可以在这里找到。

我究竟做错了什么?这只是一个糟糕的模式吗?如果是这样,我应该使用什么模式?

是的,一点没错。如果您想公开异步和同步 API,请使用正确的 api,这不会让您在第一种情况下陷入这种情况(死锁)。例如,如果您想公开 synchronous DownloadString,请WebClient改用。