1

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

    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同步代码而不会有死锁风险?

4

2 回答 2

4

您遇到了我在博客中描述的僵局。

阻止方法没有标准解决方案async,因为没有适用于所有情况的解决方案。唯一官方推荐的解决方案是async一路使用。Stephen Toub 在这里对解决方法进行了很好的总结

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

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

于 2013-08-31T21:57:13.787 回答
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 线程上恢复,因此不会出现死锁。如果您使用awaitinGetAsync()或它调用的方法,您也需要在那里做同样的事情。

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

于 2013-08-31T21:57:33.417 回答