简体   繁体   English

将 SynchronizationContext 设置为 null 而不是使用 ConfigureAwait(false)

[英]Set SynchronizationContext to null instead of using ConfigureAwait(false)

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.我无法控制异步方法(它采用异步/ 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?这是否会产生与 MyMethodAsync 在其所有 await 上使用ConfigureAwait(false)相同的效果? Are there some other problems with this approach that I'm overlooking?我忽略了这种方法还有其他一些问题吗?

(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)在其中任何一个上)

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 .如果您调用使用async / await的异步方法,死锁应该不会有任何问题。 If it doesn't use ConfigureAwait(false) , then it's not as efficient as it could be, that's all.如果它不使用ConfigureAwait(false) ,那么它的效率就不会那么高,仅此而已。 Deadlocks due to ConfigureAwait(false) only apply when you're trying to do sync-over-async (ie, if you're calling the synchronous APIs from that library).由于ConfigureAwait(false)导致的死锁仅在您尝试执行异步同步时适用(即,如果您从该库调用同步 API)。

So, the easiest and simplest solution is to just ignore the synchronous APIs, which are incorrectly designed anyway:因此,最简单和最简单的解决方案是忽略同步 API,它们无论如何都是错误设计的:

return await MyMethodAsync();

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

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).然后是繁荣,难以诊断生产问题(死锁)。
  • 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, whereas SetSynchronizationContext will not cost you anything, in addition to the optimizations it makes by not resuming on the context constantly.如果事实证明在您认为存在的地方没有阻塞/死锁,或者它已被修复, Task.Run现在将通过异步引入阻塞,而SetSynchronizationContext不会花费您任何费用,除了它通过 not 进行的优化之外不断地恢复上下文。

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 生态系统。

Setting the SynchronizationContext to null seems hacky for me.SynchronizationContext设置为 null 对我来说似乎很棘手。 Instead you can really delegate the work to threadpool.相反,您可以真正将工作委托给线程池。 Use Task.Run ..使用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代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM