I have a library that exposes synchronous and asynchronous versions of a method, but under the hood, they both have to call an async method. I can't control that async method (it uses async/await and does not use ConfigureAwait(false)
), nor can I replace it.
我有一个库,它暴露了方法的同步和异步版本,但是在引擎盖下,它们都必须调用异步方法。我无法控制该异步方法(它使用async / await并且不使用ConfigureAwait(false)),也无法替换它。
The code executes in the context of an ASP .NET request, so to avoid deadlocks, here's what I've done:
代码在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);
}
Does this produce the same effect as if MyMethodAsync had used ConfigureAwait(false)
on all of its await's? Are there some other problems with this approach that I'm overlooking?
这是否会产生与MyMethodAsync在其所有await上使用ConfigureAwait(false)相同的效果?我忽略了这种方法还有其他一些问题吗?
(MyMethodAsync is completely unaware that it's being run in an ASP .NET context, it doesn't do any calls to HttpContext.Current
or anything like that. It just does some async SQL calls, and the writer didn't put ConfigureAwait(false)
on any of them)
(MyMethodAsync完全没有意识到它是在ASP .NET上下文中运行的,它不会对HttpContext.Current或类似的东西进行任何调用。它只是执行一些异步SQL调用,而编写器没有放置ConfigureAwait(false) )他们中的任何一个)
3 个解决方案
#1
1
Provided you wrap this technique in a suitably named static function, I think your suggest is significantly better than Task.Run
, even if still the lesser of two evils.
如果你将这个技术包装在一个适当命名的静态函数中,我认为你的建议明显优于Task.Run,即使它仍然是两个邪恶中较小的一个。
Task.Run
has a number of issues:
Task.Run有很多问题:
- It is not clear why you are using it, you want to start a new task on a web server? This will be deleted by new developers fast if there are no comments. And then boom, difficult to diagnose production issues (deadlocks).
- 目前尚不清楚为什么要使用它,你想在Web服务器上启动新任务?如果没有评论,这将被新开发者快速删除。然后繁荣,难以诊断生产问题(死锁)。
- It starts on a new thread pool thread when it doesn't need to until it reaches its first await completed continuation.
- 它在新的线程池线程开始时不需要,直到它到达第一个等待完成的延续。
- It makes you block synchronously for the entire Task returning function, when from your description of the problem, the blocking is actually just part of the overall task. What is being encouraged here is longer blocking over async code, this is certainly not what you want.
- 它使您可以同步阻止整个任务返回功能,从您对问题的描述来看,阻塞实际上只是整个任务的一部分。这里鼓励的是对异步代码的阻塞时间越长,这肯定不是你想要的。
- If you use it multiple levels, you are multiplying the problem (with
SetSynchronizationContext
there's no harm in doing it more than once). - 如果您使用多个级别,则会使问题倍增(使用SetSynchronizationContext,多次执行此操作无害)。
- If it turns out that there was no blocking / deadlock where you thought there was, or it had been fixed,
Task.Run
now is introducing blocking over async, whereasSetSynchronizationContext
will not cost you anything, in addition to the optimizations it makes by not resuming on the context constantly. - 如果事实证明没有阻塞/死锁你认为存在,或者它已被修复,那么Task.Run现在正在引入阻塞而不是异步,而SetSynchronizationContext将不会花费你任何东西,除了它所做的优化之外不断恢复上下文。
I also understand there is hesitance to make any recommendation given blocking on async code should be avoided at all costs, however you have made it clear you are aware of this and this is to fix a known case of this outside of your immediate control. I think the dogmatic attitude towards this topic is damaging to the .NET ecosystem.
我也理解,应该不惜一切代价避免在异步代码上阻止提出任何建议,但是你已经明确表示你知道这一点,这是为了解决这个问题,你可以立即控制。我认为对这个主题的教条态度是对.NET生态系统的破坏。
#2
5
I have a library that exposes synchronous and asynchronous versions of a method, but under the hood, they both have to call an async method.
我有一个库,它暴露了方法的同步和异步版本,但是在引擎盖下,它们都必须调用异步方法。
The library is wrong to expose a synchronous version. Just pretend the synchronous API doesn't exist.
该库暴露同步版本是错误的。只是假装同步API不存在。
so to avoid deadlocks
所以要避免死锁
There shouldn't be any problems with deadlocks if you call an asynchronous method that uses async
/await
. If it doesn't use ConfigureAwait(false)
, then it's not as efficient as it could be, that's all. Deadlocks due to ConfigureAwait(false)
only apply when you're trying to do sync-over-async (i.e., if you're calling the synchronous APIs from that library).
如果调用使用async / await的异步方法,则死锁应该没有任何问题。如果它不使用ConfigureAwait(false),那么它就没有那么高效,就是这样。由于ConfigureAwait(false)导致的死锁仅在您尝试执行异步同步时(即,如果您从该库调用同步API)。
So, the easiest and simplest solution is to just ignore the synchronous APIs, which are incorrectly designed anyway:
因此,最简单和最简单的解决方案是忽略同步API,这些API设计不正确:
return await MyMethodAsync();
#3
2
Setting the SynchronizationContext
to null seems hacky for me. Instead you can really delegate the work to threadpool. Use Task.Run
..
将SynchronizationContext设置为null对我来说似乎很烦人。相反,您可以将工作委托给线程池。使用Task.Run ..
var result = Task.Run(() => MyMethodAsync()).Result;
or
要么
var result = Task.Run(async () => await MyMethodAsync()).Result;
This avoids the deadlock and eliminates the hacky code as well.
这可以避免死锁并消除hacky代码。
#1
1
Provided you wrap this technique in a suitably named static function, I think your suggest is significantly better than Task.Run
, even if still the lesser of two evils.
如果你将这个技术包装在一个适当命名的静态函数中,我认为你的建议明显优于Task.Run,即使它仍然是两个邪恶中较小的一个。
Task.Run
has a number of issues:
Task.Run有很多问题:
- It is not clear why you are using it, you want to start a new task on a web server? This will be deleted by new developers fast if there are no comments. And then boom, difficult to diagnose production issues (deadlocks).
- 目前尚不清楚为什么要使用它,你想在Web服务器上启动新任务?如果没有评论,这将被新开发者快速删除。然后繁荣,难以诊断生产问题(死锁)。
- It starts on a new thread pool thread when it doesn't need to until it reaches its first await completed continuation.
- 它在新的线程池线程开始时不需要,直到它到达第一个等待完成的延续。
- It makes you block synchronously for the entire Task returning function, when from your description of the problem, the blocking is actually just part of the overall task. What is being encouraged here is longer blocking over async code, this is certainly not what you want.
- 它使您可以同步阻止整个任务返回功能,从您对问题的描述来看,阻塞实际上只是整个任务的一部分。这里鼓励的是对异步代码的阻塞时间越长,这肯定不是你想要的。
- If you use it multiple levels, you are multiplying the problem (with
SetSynchronizationContext
there's no harm in doing it more than once). - 如果您使用多个级别,则会使问题倍增(使用SetSynchronizationContext,多次执行此操作无害)。
- If it turns out that there was no blocking / deadlock where you thought there was, or it had been fixed,
Task.Run
now is introducing blocking over async, whereasSetSynchronizationContext
will not cost you anything, in addition to the optimizations it makes by not resuming on the context constantly. - 如果事实证明没有阻塞/死锁你认为存在,或者它已被修复,那么Task.Run现在正在引入阻塞而不是异步,而SetSynchronizationContext将不会花费你任何东西,除了它所做的优化之外不断恢复上下文。
I also understand there is hesitance to make any recommendation given blocking on async code should be avoided at all costs, however you have made it clear you are aware of this and this is to fix a known case of this outside of your immediate control. I think the dogmatic attitude towards this topic is damaging to the .NET ecosystem.
我也理解,应该不惜一切代价避免在异步代码上阻止提出任何建议,但是你已经明确表示你知道这一点,这是为了解决这个问题,你可以立即控制。我认为对这个主题的教条态度是对.NET生态系统的破坏。
#2
5
I have a library that exposes synchronous and asynchronous versions of a method, but under the hood, they both have to call an async method.
我有一个库,它暴露了方法的同步和异步版本,但是在引擎盖下,它们都必须调用异步方法。
The library is wrong to expose a synchronous version. Just pretend the synchronous API doesn't exist.
该库暴露同步版本是错误的。只是假装同步API不存在。
so to avoid deadlocks
所以要避免死锁
There shouldn't be any problems with deadlocks if you call an asynchronous method that uses async
/await
. If it doesn't use ConfigureAwait(false)
, then it's not as efficient as it could be, that's all. Deadlocks due to ConfigureAwait(false)
only apply when you're trying to do sync-over-async (i.e., if you're calling the synchronous APIs from that library).
如果调用使用async / await的异步方法,则死锁应该没有任何问题。如果它不使用ConfigureAwait(false),那么它就没有那么高效,就是这样。由于ConfigureAwait(false)导致的死锁仅在您尝试执行异步同步时(即,如果您从该库调用同步API)。
So, the easiest and simplest solution is to just ignore the synchronous APIs, which are incorrectly designed anyway:
因此,最简单和最简单的解决方案是忽略同步API,这些API设计不正确:
return await MyMethodAsync();
#3
2
Setting the SynchronizationContext
to null seems hacky for me. Instead you can really delegate the work to threadpool. Use Task.Run
..
将SynchronizationContext设置为null对我来说似乎很烦人。相反,您可以将工作委托给线程池。使用Task.Run ..
var result = Task.Run(() => MyMethodAsync()).Result;
or
要么
var result = Task.Run(async () => await MyMethodAsync()).Result;
This avoids the deadlock and eliminates the hacky code as well.
这可以避免死锁并消除hacky代码。