如何安全地等待异步方法
本文关键字:等待 异步方法 安全 何安全 | 更新日期: 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)
是个好主意。