ConfigureAwait (false) versus setting the synchronization context to null

I often see the recommended library for asynchronous code that we should use ConfigureAwait(false) for all asynchronous calls to avoid situations where the return of our call will be scheduled in the user interface thread or in the context of synchronizing the web request, which causes problems with other deadlocks things.

One of the problems with using ConfigureAwait(false) is that this is not something you can just do at the library call entry point. In order for it to be effective, it must run all the way to the stack throughout your library code.

It seems to me that a viable alternative is simply to set the current synchronization context to zero in public access points at the top level of the library and just forget about ConfigureAwait(false) . However, I do not see many examples of how people accept or recommend this approach.

Is there anything wrong just setting the current synchronization context to zero at the library entry points? Are there any potential problems with this approach (with the possible exception of a slight performance improvement when there is a pending message in the default synchronization context)?

(EDIT # 1) Adding some sample code, which I mean:

  public class Program { public static void Main(string[] args) { SynchronizationContext.SetSynchronizationContext(new LoggingSynchronizationContext(1)); Console.WriteLine("Executing library code that internally clears synchronization context"); //First try with clearing the context INSIDE the lib RunTest(true).Wait(); //Here we again have the context intact Console.WriteLine($"After First Call Context in Main Method is {SynchronizationContext.Current?.ToString()}"); Console.WriteLine("\nExecuting library code that does NOT internally clear the synchronization context"); RunTest(false).Wait(); //Here we again have the context intact Console.WriteLine($"After Second Call Context in Main Method is {SynchronizationContext.Current?.ToString()}"); } public async static Task RunTest(bool clearContext) { Console.WriteLine($"Before Lib call our context is {SynchronizationContext.Current?.ToString()}"); await DoSomeLibraryCode(clearContext); //The rest of this method will get posted to my LoggingSynchronizationContext //But....... if(SynchronizationContext.Current == null){ //Note this will always be null regardless of whether we cleared it or not Console.WriteLine("We don't have a current context set after return from async/await"); } } public static async Task DoSomeLibraryCode(bool shouldClearContext) { if(shouldClearContext){ SynchronizationContext.SetSynchronizationContext(null); } await DelayABit(); //The rest of this method will be invoked on the default (null) synchronization context if we elected to clear the context //Or it should post to the original context otherwise Console.WriteLine("Finishing library call"); } public static Task DelayABit() { return Task.Delay(1000); } } public class LoggingSynchronizationContext : SynchronizationContext { readonly int contextId; public LoggingSynchronizationContext(int contextId) { this.contextId = contextId; } public override void Post(SendOrPostCallback d, object state) { Console.WriteLine($"POST TO Synchronization Context (ID:{contextId})"); base.Post(d, state); } public override void Send(SendOrPostCallback d, object state) { Console.WriteLine($"Post Synchronization Context (ID:{contextId})"); base.Send(d, state); } public override string ToString() { return $"Context (ID:{contextId})"; } } 

Fulfillment of this will be issued:

 Executing library code that internally clears synchronization context Before Lib call our context is Context (ID:1) Finishing library call POST TO Synchronization Context (ID:1) We don't have a current context set after return from async/await After First Call Context in Main Method is Context (ID:1) Executing library code that does NOT internally clear the synchronization context Before Lib call our context is Context (ID:1) POST TO Synchronization Context (ID:1) Finishing library call POST TO Synchronization Context (ID:1) We don't have a current context set after return from async/await After Second Call Context in Main Method is Context (ID:1) 

All this works as I expected, but I do not come across the recommendations of libraries to do this inside. I find that requiring every internal waiting point to be invoked using ConfigureAwait(false) is annoying, and even one skipped ConfigureAwait() can cause problems throughout the application. It seems like this would solve the problem simply at the public library entry point with one line of code. What am I missing?

(EDIT # 2)

Based on some feedback from Alexei, it seems that I did not consider the possibility that the task would not be immediately expected. Since the execution context is fixed while waiting (and not asynchronously), this means that the change to SynchronizationContext.Current will not be isolated from the library method. Based on this, it would seem that this is enough to force us to capture the context by wrapping the internal logic of the library with a call that makes us wait. For instance:

  async void button1_Click(object sender, EventArgs e) { var getStringTask = GetStringFromMyLibAsync(); this.textBox1.Text = await getStringTask; } async Task<string> GetStringFromMyLibInternal() { SynchronizationContext.SetSynchronizationContext(null); await Task.Delay(1000); return "HELLO WORLD"; } async Task<string> GetStringFromMyLibAsync() { //This forces a capture of the current execution context (before synchronization context is nulled //This means the caller context should be intact upon return //even if not immediately awaited. return await GetStringFromMyLibInternal(); } 

(EDIT # 3)

Based on a discussion of Steven Cleary's answer. There are some problems with this approach. But we can take a similar approach by wrapping the library call in a non-asynchronous method that still returns the task, but takes care of returning the syncrhonization context at the end. (Note that this uses the SynchronizationContextSwitcher from the Stephen AsyncEx library.

  async void button1_Click(object sender, EventArgs e) { var getStringTask = GetStringFromMyLibAsync(); this.textBox1.Text = await getStringTask; } async Task<string> GetStringFromMyLibInternal() { SynchronizationContext.SetSynchronizationContext(null); await Task.Delay(1000); return "HELLO WORLD"; } Task<string> GetStringFromMyLibAsync() { using (SynchronizationContextSwitcher.NoContext()) { return GetStringFromMyLibInternal(); } //Context will be restored by the time this method returns its task. } 
+6
source share
2 answers

I often see the recommended library for asynchronous code that we should use ConfigureAwait (false) for all asynchronous calls to avoid situations where the return of our call will be scheduled in the user interface thread or web request synchronization context, causing problems with deadlocks, among other things.

I recommend ConfigureAwait(false) because it (correctly) notes that the calling context is not required. It also gives you a slight performance advantage. While ConfigureAwait(false) can prevent deadlocks, i.e. not for the intended purpose.

It seems to me that a viable alternative is simply to set the current synchronization context to zero in public access points at the top level of the library and just forget about ConfigureAwait (false).

Yes, this is an option. However, it will not completely avoid deadlocks because await will try to resume on TaskScheduler.Current if there is no current SynchronizationContext .

In addition, it is incorrect to create a library that replaces an infrastructure-level component.

But you can do it if you want. Just remember to return it to its original value at the end.

Oh, another mistake: there are APIs that assume that the current SyncCtx is what is provided for this structure. Some ASP.NET APIs are similar. So, if you call back the end-user code, this can be a problem. But in this case, you must explicitly document in which context their callbacks are invoked anyway.

However, I do not see many cases where people accept or recommend this approach.

It is becoming more popular. It is enough that I add an API for this in my AsyncEx library :

 using (SynchronizationContextSwitcher.NoContext()) { ... } 

I have not used this technique either.

Are there any potential problems with this approach (other than a possible slight performance improvement when there is a message waiting in the default synchronization context)?

Actually, this is a slight increase in productivity.

+7
source

The synchronization context is similar to a static variable and changes it without recovery before the control leaves your method, which will lead to unexpected behavior.

I do not believe that you can safely set the current context of thread synchronization inside the library function, that await something like restoring the context in the middle of the code generated by the compiler is not really possible.

Example:

  async Task<int> MyLibraryMethodAsync() { SynchronizationContext.SetSynchronizationContext(....); await SomeInnerMethod(); // note that method returns at this point // maybe restore synchronization context here... return 42; } ... // code that uses library, runs on UI thread void async OnButtonClick(...) { // <-- context here is UI-context, code running on UI thread Task<int> doSomething = MyLibraryMethodAsync(); // <-- context here is set by MyLibraryMethod - ie null, code running on UI thread var textFromFastService = await FastAsync(); // <-- context here is set by MyLibraryMethod, code running on pool thread (non-UI) textBox7.Text = textFromFastService; // fails... var get42 = await doSomething; } 
+1
source

Source: https://habr.com/ru/post/1014276/


All Articles