Exactly what impact performance-wise will this create?
Almost none.
Will there be any Thread context switch?
Not any more than normal.
As I describe on my blog, when an await
decides to yield, it will first capture the current context (SynchronizationContext.Current
, unless it is null
, in which case the context is TaskScheduler.Current
). Then, when the task completes, the async
method resumes executing in that context.
The other important element in this conversation is that task continuations execute synchronously if possible (again, described on my blog). Note that this is not actually documented anywhere; it's an implementation detail.
ConfigureAwait(false)
everywhere in domain code does not feel like a viable solution
You can use ConfigureAwait(false)
in your domain code (and I actually recommend you do, for semantic reasons), but it probably will not make any difference performance-wise in this scenario. Here's why...
Let's consider how this call works within your application. You've undoubtedly got some entry-level code that depends on the context - say, a button click handler or an ASP.NET MVC action. This calls into the code in question - domain code that performs an "asynchronous cast". This in turn calls into low-level code which is already using ConfigureAwait(false)
.
If you use ConfigureAwait(false)
in your domain code, the completion logic will look like this:
- Low-level task completes. Since this is probably I/O-based, the task completion code is running on a thread pool thread.
- The low-level-task completion code resumes executing the domain-level code. Since the context was not captured, the domain-level code (the cast) executes on the same thread pool thread, synchronously.
- The domain-level code reaches the end of its method, which completes the domain-level task. This task completion code is still running on the same thread pool thread.
- The domain-level-task completion code resumes executing the entry-level code. Since the entry level requires the context, the entry-level code is queued to that context. In a UI app, this causes a thread switch to the UI thread.
If you don't use ConfigureAwait(false)
in your domain code, the completion logic will look like this:
- Low-level task completes. Since this is probably I/O-based, the task completion code is running on a thread pool thread.
- The low-level-task completion code resumes executing the domain-level code. Since the context was captured, the domain-level code (the cast) is queued to that context. In a UI app, this causes a thread switch to the UI thread.
- The domain-level code reaches the end of its method, which completes the domain-level task. This task completion code is running in the context.
- The domain-level-task completion code resumes executing the entry-level code. The entry level requires the context, which is already present.
So, it's just a matter of when that context switch takes place. For UI apps, it is nice to keep small amounts of work on the thread pool for as long as possible, but for most apps it won't hurt performance if you don't. Similarly, for ASP.NET apps, you can get tiny amounts of parallelism for free if you keep as much code as possible out of the request context, but for most apps this won't matter.