Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

The code executes in the context of an ASP .NET request, so to avoid deadlocks, here's what I've done:

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 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)

like image 324
Ryan Avatar asked Aug 02 '14 12:08

Ryan


People also ask

Is ConfigureAwait false by default?

To improve performance and avoid potential deadlocks, use ConfigureAwait(false) in any non-UI code. The exception here is app-level code, such as Windows Forms, WPF, and ASP.NET. ConfigureAwait(true) corresponds to the default behavior and does nothing meaningful, therefore such calls can be safely omitted.

Do I need ConfigureAwait false?

As a general rule, every piece of code that is not in a view model and/or that does not need to go back on the main thread should use ConfigureAwait false. This is simple, easy and can improve the performance of an application by freeing the UI thread for a little longer.

Should I use ConfigureAwait false .NET core?

NET Core you won't need to spread ConfigureAwait(false) all over your code. Almost! This is almost true, it is still recommended the utilization of ConfigureAwait(false) for libraries as a fallback if those libraries are used within a legacy framework. But for most of the cases yes, in .

What does ConfigureAwait false mean?

ConfigureAwait(false) is about not returning back to the synchronization context when an awaiter completes. It's about performance, not deadlocks.


Video Answer


2 Answers

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.

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).

So, the easiest and simplest solution is to just ignore the synchronous APIs, which are incorrectly designed anyway:

return await MyMethodAsync();
like image 158
Stephen Cleary Avatar answered Sep 18 '22 13:09

Stephen Cleary


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 has a number of issues:

  • 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).
  • 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.

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.

like image 39
Stuart Avatar answered Sep 22 '22 13:09

Stuart