将SynchronizationContext设置为null,而不是使用ConfigureAwait(false)
本文关键字:ConfigureAwait false 设置 SynchronizationContext null | 更新日期: 2023-09-27 18:16:00
我有一个库,它公开了一个方法的同步和异步版本,但在底层,它们都必须调用异步方法。我不能控制async方法(它使用async/await并且不使用ConfigureAwait(false)
),我也不能替换它。
代码在asp.net请求的上下文中执行,因此为了避免死锁,我这样做:
var capturedContext = SynchronizationContext.Current;
try
{
// Wipe the sync context, so that the bad library code won't find it
// That way, we avoid the deadlock
SynchronizationContext.SetSynchronizationContext(null);
// Call the async method and wait for the result
var task = MyMethodAsync();
task.Wait();
// Return the result
return task.Result;
}
finally
{
// Restore the sync context
SynchronizationContext.SetSynchronizationContext(capturedContext);
}
这是否产生相同的效果,如果MyMethodAsync使用ConfigureAwait(false)
所有的等待?这种方法是否还有我忽略的其他问题?
(MyMethodAsync完全不知道它正在asp.net上下文中运行,它不做任何调用HttpContext.Current
或类似的东西。它只是做一些异步SQL调用,作者没有把ConfigureAwait(false)
放在他们中的任何一个)
我有一个库,它公开了一个方法的同步和异步版本,但在底层,它们都必须调用异步方法。
标准库公开同步版本是错误的。就当同步API不存在。
如果你调用一个使用以避免死锁
async
/await
的异步方法,应该不会有死锁的问题。如果它不使用ConfigureAwait(false)
,那么它就没有达到应有的效率,仅此而已。ConfigureAwait(false)
导致的死锁只在你尝试做sync-over-async(即,如果你从该库调用同步api)时才会发生。
因此,最简单的解决方案就是忽略同步api,因为它们的设计是错误的:
return await MyMethodAsync();
如果您将此技术包装在一个适当命名的静态函数中,我认为您的建议明显优于Task.Run
,即使两者相权取其轻。
Task.Run
有一些问题:
- 不清楚你为什么要使用它,你想在web服务器上启动一个新任务?如果没有评论,这将被新开发人员快速删除。然后,很难诊断生产问题(死锁)。
- 当它到达第一个等待完成的continuation时,它在一个新的线程池线程上启动。
- 它使您同步阻塞整个Task返回函数,而从您对问题的描述中,阻塞实际上只是整个任务的一部分。这里鼓励的是更长阻塞异步代码,这肯定不是你想要的。
- 如果你使用它的多个级别,你是成倍的问题(与
SetSynchronizationContext
没有伤害做它不止一次)。 - 如果事实证明没有阻塞/死锁,你认为有,或者它已经被修复,
Task.Run
现在是异步引入阻塞,而SetSynchronizationContext
不会花费你任何东西,除了优化它不恢复上下文不断。
我也理解有犹豫作出任何建议给定阻塞异步代码应该不惜一切代价避免,但是你已经明确表示,你知道这一点,这是修复这个在你的直接控制之外的已知情况。我认为对这个话题的教条态度正在破坏。net生态系统。
将SynchronizationContext
设置为null对我来说似乎很粗糙。相反,您可以将工作委派给线程池。使用Task.Run
..
var result = Task.Run(() => MyMethodAsync()).Result;
或
var result = Task.Run(async () => await MyMethodAsync()).Result;
这避免了死锁并消除了黑客代码。