Synchronization Context in Async-Await
Pseudocode for async/await keywords
Consider a simple asynchronous method:
async Task Foo()
{
Bar();
await Baz();
Qux();
}
Simplifying, we can say that this code actually means the following:
Task Foo()
{
Bar();
Task t = Baz();
var context = SynchronizationContext.Current;
t.ContinueWith(task) =>
{
if (context == null)
Qux();
else
context.Post((obj) => Qux(), null);
}, TaskScheduler.Current);
return t;
}
It means that async
/await
keywords use current synchronization context if it exists. I.e. you can write library code that would work correctly in UI, Web, and Console applications.
Disabling synchronization context
To disable synchronization context you should call the ConfigureAwait
method:
async Task() Foo()
{
await Task.Run(() => Console.WriteLine("Test"));
}
. . .
Foo().ConfigureAwait(false);
ConfigureAwait provides a means to avoid the default SynchronizationContext capturing behavior; passing false for the flowContext parameter prevents the SynchronizationContext from being used to resume execution after the await.
Quote from It’s All About the SynchronizationContext.
Why SynchronizationContext is so important?
Consider this example:
private void button1_Click(object sender, EventArgs e)
{
label1.Text = RunTooLong();
}
This method will freeze UI application until the RunTooLong
will be completed. The application will be unresponsive.
You can try run inner code asynchronously:
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() => label1.Text = RunTooLong());
}
But this code won’t execute because inner body may be run on non-UI thread and it shouldn’t change UI properties directly:
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
var label1Text = RunTooLong();
if (label1.InvokeRequired)
lable1.BeginInvoke((Action) delegate() { label1.Text = label1Text; });
else
label1.Text = label1Text;
});
}
Now don’t forget always to use this pattern. Or, try SynchronizationContext.Post
that will make it for you:
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
var label1Text = RunTooLong();
SynchronizationContext.Current.Post((obj) =>
{
label1.Text = label1 Text);
}, null);
});
}