如何安全地等待异步方法

本文关键字:等待 异步方法 安全 何安全 | 更新日期: 2023-09-27 18:29:50

我有一个看起来像这样的方法:

    static async Task<string> GetGooglePage(ProxyInfo proxy)
    {
        using (var m=new MyNetworkClass())
        {
            m.Proxy = proxy;
            return await m.GetAsync("http://www.google.com");
        }
    }

现在我想从非异步的方法调用它,并得到结果。

我尝试的方式是这样的:

        foreach (var proxy in proxys)
        {
            try
            {
                GetGooglePage(proxy.ToProxyInfo()).Wait();
            }
            catch
            {}
            lock (Console.Out)
            {
                Console.Write(current++ + "'r");
            }
        }

我的问题是有时GetGooglePage(proxy.ToProxyInfo()).Wait();会死锁(根据visualstudio调试器的说法,除了这个调用之外没有堆栈)。

我不想一直使用异步到Main()。如何在不冒死锁风险的情况下从同步代码正确调用GetGooglePage

如何安全地等待异步方法

我在博客中描述了您遇到的死锁。

async方法上没有阻塞的标准解决方案,因为没有适用于所有情况的解决方案。唯一官方推荐的解决方案是始终使用async。Stephen Toub在这里总结了很多变通方法。

一种方法是在后台线程中执行async方法(通过Task.Run),然后在该线程上执行Wait。这种方法的缺点是:1)它不适用于需要特定上下文的async方法(例如,写入ASP.NET响应或更新UI);2) 从同步的上下文改变为未同步的线程池上下文可能引入竞争条件;以及3)它烧了一个线程,只是在等待另一个线程。

另一种方法是在嵌套消息循环中执行async方法。这种方法的缺点是:1)"嵌套消息循环"对于每个平台都是不同的(WPF与WinForms等);和2)嵌套循环引入了可重入性问题(这是Win32时代许多错误的原因)。

最好的选择是不这样做:如果同步等待异步方法,那么该方法一开始就没有异步的理由。因此,您应该做的(假设您真的想或必须使顶级方法同步)是使所有方法同步。

如果不能做到这一点,那么可以使用ConfigureAwait(false):来防止死锁

static async Task<string> GetGooglePage(ProxyInfo proxy)
{
    using (var m=new MyNetworkClass())
    {
        m.Proxy = proxy;
        return await m.GetAsync("http://www.google.com").ConfigureAwait(false);
    }
}

这样,当方法恢复时,它不会尝试在UI线程上恢复,因此不会出现死锁。如果你在GetAsync()中使用await或它调用的方法,你也需要在那里做同样的事情。

一般来说,只要不需要恢复UI线程,那么在任何地方都使用ConfigureAwait(false)是个好主意。