I have been using the
Task-based asynchronous programming model introduced in C# 5 (.NET 4.5) for a while now and it truly changed my
life (at least as far as writing
async code is concerned!). No more callbacks and non-linear flow. The code we write with the
async/await paradigm is very similar to what we would write in a synchronously fashion which makes it far easier to follow and understand.
There is a tone of documentation and articles out there on how to use asynchronous programming.
So much documentation in-fact, that some key features can be easily overseen. There is one feature especially that I only discovered by coincidence a few weeks ago:
When an incompleted
Task is awaited, the current
context is captured and used to resume the method when the task is completed (e.g. after the await keywords).
context is null if called from a thread that is not the
UI thread. Otherwise it returns the UI specific context depending on the platform you are using (WinForm, WPF, ASP.NET have different implementations)
context switch between
UI thread context and
worker thread context can cause performance issues. Your app may feel less responsive as the amount of asynchronous code that you are writing grows…which is exactly what we are trying to avoid when writing asynchronous code.
By using the
ConfigureAwait defined in the
Task class and passing it the value
continueOnCapturedContext:false, you can enable some parallelism and ensure that code can continue working on
a worker thread instead of being marshaled back to the original context and potentially be executed on the
There are a few rules to know to avoid any surprises:
- UI should always be updated on the
UI thread. That means that you should not use
ConfigureAwait(false)when the code immediately after the
awaitupdates the UI
asyncmethod has its own context. That means that the calling method is not affected by
ConfigureAwait(e.g. if one async method calls another one, their contexts are independent)
ConfigureAwaitcan return back on the original thread if the awaited task completes immediately or is already completed
With this rules in mind, my suggestion is to always separate the
context-dependent code from the
context-free code. The goal is to minimize the
context-dependent (typically event handlers) code by creating methods, which a context of their own.
Let’s take the following code (all in one file for simplicity), simulating the update of a
ListView from of an hypothetical list of songs:
This code could be updated to follow our best-practices like so:
Another option would be to switch back to the
UI-thread, only when UI updates are required and stay on the worker thread(s) otherwise.
A typical scenario would be to display a progress bar showing the number of tasks completed, while those tasks are being processed in the background. We will explore different options for switching to the
UI-thread in a future post.