Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ConfigureAwait(false) vs setting sync context to null

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

One of the problems with using ConfigureAwait(false) is that it isn't something you can just do on the entry point of your library call. In order for it to be effective it must be done all the way down the stack throughout your library code.

It seems to me that a viable alternative is to simply set the current synchronization context to null at the top-level public-facing entry points of the library, and just forget about ConfigureAwait(false). However, I don't see many instances of people taking or recommending this approach.

Is there anything wrong with simply setting the current synchronization context to null on the library entry points? Are there any potential problems with this approach (other than the possible insignificant performance hit of having the await post to the default synchronization context)?

(EDIT #1) Adding some example code of what 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})";
        }
    }

The execution of this will output:

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)

This all works like I would expect, but I don't come across people recommending libraries do this internally. I find that requiring every internal await point be called with ConfigureAwait(false) is annoying, and even one missed ConfigureAwait() can cause trouble throughout an application. This seems like it would solve the issue simply at the public entry-point of the library with a single line of code. What am I missing?

(EDIT #2)

Based on some feedback from Alexei's answer, it seems I hadn't consider the possibility of a task not being immediately awaited. Since the execution context is captured at the time of the await (not the time of the async call), that would mean the change to SynchronizationContext.Current would not be isolated to the library method. Based on this it would seem that it should suffice to force a capture of the context by wrapping the internal logic of the library in a call that forces an wait. For example:

    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's context should be intact upon return
        //even if not immediately awaited.
        return await GetStringFromMyLibInternal();          
    }

(EDIT #3)

Based on the discussion on Stephen Cleary's answer. There are some problems with this approach. But we can do a similar approach by wrapping the library call in a non-async method that still returns a task, but takes care of resetting the syncrhonization context at the end. (Note this uses the SynchronizationContextSwitcher from Stephen's 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.
    }
like image 963
Daniel Tabuenca Avatar asked Jan 21 '17 06:01

Daniel Tabuenca


People also ask

Should I always use 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.

When would you not use ConfigureAwait false?

If the await task. ConfigureAwait(false) involves a task that's already completed by the time it's awaited (which is actually incredibly common), then the ConfigureAwait(false) will be meaningless, as the thread continues to execute code in the method after this and still in the same context that was there previously.

Is ConfigureAwait true 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.

When should I use ConfigureAwait?

A situation to use ConfigureAwait(true) is when performing await in a lock, or using any other context/thread specific resources. This requires a synchronization context, which you will have to create, unless you are using Windows Forms or WPF, which automatically create a UI synchronization context.


2 Answers

I often see recommended for async library code, that we should use ConfigureAwait(false) on all async calls to avoid situations where the return of our call will be scheduled on a UI thread or a web request synchronization context causing issues 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 small performance benefit. While ConfigureAwait(false) can prevent deadlocks, that is not its intended purpose.

It seems to me that a viable alternative is to simply set the current synchronization context to null at the top-level public-facing entry points of the library, and just forget about ConfigureAwait(false).

Yes, that is an option. It won't completely avoid deadlocks, though, because await will attempt to resume on TaskScheduler.Current if there's no current SynchronizationContext.

Also, it feels wrong to have a library replacing a framework-level component.

But you can do this if you want. Just don't forget to set it back to its original value at the end.

Oh, one other pitfall: there are APIs out there that will assume the current SyncCtx is what's provided for that framework. Some ASP.NET helper APIs are like that. So, if you call back end-user code, then that could be a problem. But in that case, you should explicitly document what context their callbacks are invoked in anyway.

However, I don't see many instances of people taking or recommending this approach.

It is slowly becoming more popular. Enough so that I've added an API for this in my AsyncEx library:

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

I haven't used this technique myself, though.

Are there any potential problems with this approach (other than the possible insignificant performance hit of having the await post to the default synchronization context)?

Actually, it's an insignificant performance gain.

like image 62
Stephen Cleary Avatar answered Oct 12 '22 16:10

Stephen Cleary


Synchronization Context is similar to static variable and changing it without restoring before control leaves your method will lead to unexpected behavior.

I don't believe you can safely set current thread's synchronization context inside library function that await anything as restoring context in the middle of compiler generated code is not really possible to my knowledge.

Sample:

 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 - i.e. 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;
}
like image 28
Alexei Levenkov Avatar answered Oct 12 '22 16:10

Alexei Levenkov