Initially I thought that all continuations are executed on the threadpool (given a default synchronization context). This however doesn't seem to be the case when I use a TaskCompletionSource.
Actually, when using await
, most continuations are executed synchronously.
Marc's answer is great; I just wanted to go into a bit more detail...
TaskCompletionSource<T>
by default will operate synchronously when Set*
is called. Set*
will complete the task and issue the continuations in a single method call. (This means that calling Set*
while holding a lock is a recipe for deadlocks.)
I use the weird phrase "issue the continuations" there because it may or may not actually execute them; more on that later.
The TaskCreationOptions.RunContinuationsAsynchronously
flag will tell TaskCompletionSource<T>
to issue the continuations asynchronously. This breaks apart the completing of the task (which is still done immediately by Set*
) from the issuing of the continuations (which is only triggered by the Set*
call). So with RunContinuationsAsynchronously
, a Set*
call will only complete the task; it will not execute the continuations synchronously. (This means that calling Set*
while holding a lock is safe.)
But back to the default case, which issues the continuations synchronously.
Each continuation also has a flag; by default a continuation is executed asynchronously, but it can be made synchronous by TaskContinuationOptions.ExecuteSynchronously
. (Note that await
does use this flag - link is to my blog; technically this is an implementation detail and not officially documented).
However, even if ExecuteSynchronously
is specified, there are a number of situations where the continuation is not executed synchronously:
- If there is a
TaskScheduler
associated with the continuation, that scheduler is given the option of rejecting the current thread, in which case the task is queued to that TaskScheduler
instead of executing synchronously.
- If the current thread is being aborted, then the task is queued elsewhere.
- If the current thread's stack is too deep, then the task is queued elsewhere. (This is only a heuristic, and not guaranteed to avoid
StackOverflowException
).
That's quite a few conditions, but with your simple Console app test, they're all met:
TaskCompletionSource<T>
does not specify RunContinuationsAsynchronously
.
- The continuation (
await
) does specify ExecuteSynchronously
.
- The continuation does not have a
TaskScheduler
specified.
- The target thread is able to execute the continuation (not being aborted; stack is ok).
As a general rule, I would say any usage of TaskCompletionSource<T>
should specify TaskCreationOptions.RunContinuationsAsynchronously
. Personally, I think the semantics are more appropriate and less surprising with that flag.