我有一个看起来像这样的方法:
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
而不会有死锁风险?
答案 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)
。