In this case, TaskCompletionSource.SetResult () triggers a synchronous continuation?

At first, I thought that all continuations are done in threadpool (given the default synchronization context). This, however, is not the case when I use TaskCompletionSource .

My code looks something like this:

 Task<int> Foo() { _tcs = new TaskCompletionSource<int>(); return _tcs.Task; } async void Bar() { Console.WriteLine(Thread.Current.ManagedThreadId); Console.WriteLine($"{Thread.Current.ManagedThreadId} - {await Foo()}"); } 

Bar is called in a specific thread, and TaskCompletionSource remains uninstalled for some time, which means returned tasks IsComplete = false . Then after some time the same thread will go to the call to _tcs.SetResult(x) , which, in my opinion, should continue to continue in threadpool.

But what I observed in my application is that the thread executing the continuation actually remains the same thread, as if the continuation was called synchronously as soon as SetResult is SetResult .

I even tried to set a breakpoint on SetResult and step over it (and have a breakpoint in the continuation), which in turn continues to invoke the continuation synchronously.

When exactly SetResult() immediately call a continuation synchronously?

+7
multithreading c # asynchronous async-await
source share
2 answers

SetResult usually executes extensions from TCS synchronously. The main exception: if you explicitly pass the TaskContinuationOptions.RunContinuationsAsynchronously flag when creating TCS (new in .NET 4.6). Another scenario when it works asynchronously is if it considers that the current thread is doomed.

This is very important, because if you are not careful, you can end up with the caller controlling a thread that was supposed to do other work (for example: working with socket IO).

+4
source share

At first, I thought that all continuations are done in threadpool (given the default synchronization context). This, however, does not seem to be the case when I use TaskCompletionSource.

Actually, when using await most continuations are executed synchronously.

Mark's answer is great; I just wanted a little more ...

TaskCompletionSource<T> by default will work synchronously when calling Set* . Set* will complete the task and return the extensions in a single method call. (This means that calling Set* while holding a lock is a recipe for locks.)

I use the strange phrase “release sequels” there because it may or may not actually be executed; more on this later.

The TaskCreationOptions.RunContinuationsAsynchronously flag tells TaskCompletionSource<T> to continue asynchrony. This breaks the completion of the task (which is still executed immediately by Set* ) from issuing continuations (which is caused only by calling Set* ). Thus, with RunContinuationsAsynchronously calling Set* will only do the task; it will not perform continuation synchronously. (This means that calling Set* while maintaining the lock is safe.)

But back to the default case, which synchronously issues the continuation.

Each continuation also has a flag; By default, the continuation is asynchronous, but can be made synchronous using TaskContinuationOptions.ExecuteSynchronously . (Note that await uses this flag - a link for my blog, technically this is an implementation detail and is not officially documented).

However, even if ExecuteSynchronously specified, there are a number of situations where the continuation is not performed synchronously :

  • If there is a TaskScheduler associated with the continuation, this scheduler is given the option of rejecting the current thread, in which case the task is queued on the TaskScheduler instead of synchronous execution.
  • If the current thread is interrupted, the task is queued elsewhere.
  • If the current thread stack is too deep, the task is queued elsewhere. (This is only a heuristic and is not guaranteed to avoid a StackOverflowException ).

These are quite a few conditions, but they all come across with your simple test console application Console:

  • TaskCompletionSource<T> does not indicate RunContinuationsAsynchronously .
  • A continuation ( await ) indicates ExecuteSynchronously .
  • The sequel does not specify TaskScheduler .
  • The target thread is able to continue (not interrupted, the stack is in order).

Generally, I would say that any use of TaskCompletionSource<T> should indicate TaskCreationOptions.RunContinuationsAsynchronously . Personally, I believe that semantics are more appropriate and less surprising with this flag.

+5
source share

All Articles