如何安全地等待异步方法?

时间:2013-08-31 20:27:02

标签: c# asynchronous async-await

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

    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();死锁(根据visual studio调试器,除了这个调用之外没有堆栈)。

我不希望一直使用async到Main()。如何从同步代码中正确调用GetGooglePage而不会有死锁风险?

2 个答案:

答案 0 :(得分:4)

你遇到了我describe on my blog的僵局。

async方法上没有阻止标准的解决方案,因为没有适用于所有情况的解决方案。唯一正式推荐的解决方案是一直使用async。 Stephen Toub有good summary of workarounds here

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

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

答案 1 :(得分:2)

最好的选择不是这样做:如果你同步等待异步方法,那么首先没有理由让该方法异步。那么,你应该做什么(假设你真的想要或必须使顶级方法同步)是让所有方法同步。

如果你不能这样做,那么你可以使用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线程上恢复,因此您不会遇到死锁。如果您在await中使用GetAsync()或其调用的方法,那么您也需要做同样的事情。

一般情况下,只要您不需要恢复UI线程,最好在任何地方使用ConfigureAwait(false)