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");
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() {
(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(); }