Coming from Task.Run doesn't execute and after having read TaskContinuationOptions.RunContinuationsAsynchronously and Stack Dives, I wanted to confirm that I got this right:
When used for a TaskCompletionSource
:
If
TaskCreationOptions.RunContinuationsAsynchronously
is set, thenTaskCompletionSource.SetResult()
will send all continuations to the thread pool where they will be queued and processed, maybe similar to wrapping them all inTask.Run()
calls. The continuations will be processed asynchronously, on parallel thread pool threads, not blocking the current thread/task.If
TaskCreationOptions.RunContinuationsAsynchronously
is not set (the default, I'm assuming), then all continuations (= delegates inTask.ContinueWith()
calls) are executed synchronously, one after the other on the thread that calledTaskCompletionSource.SetResult()
, blocking that current thread/task until all continuations have been processed, maybe similar to the way event delegates are processed.
I wrote the following test to try and confirm these assumptions:
[TestMethod]
public void TestTaskCreationOptions()
{
void callback(int i, int sleep)
{
Console.WriteLine($"{i} {System.Threading.Thread.CurrentThread.Description()} - before sleep {sleep}");
System.Threading.Thread.Sleep(sleep);
Console.WriteLine($"{i} {System.Threading.Thread.CurrentThread.Description()} - after sleep {sleep}");
}
void test(TaskCompletionSource<object> tcs)
{
Console.WriteLine($"starting: {tcs.Task.CreationOptions}");
tcs.Task.ContinueWith((t) => callback(1,1000));
tcs.Task.ContinueWith((t) => callback(2,300));
tcs.Task.ContinueWith((t) => callback(3,300));
Task.Run(() =>
{
callback(-1,100);
tcs.SetResult(null);
callback(-2,100);
});
Console.WriteLine("waiting");
Thread.Sleep(5000);
Console.WriteLine("done");
}
test(new TaskCompletionSource<object>( TaskCreationOptions.RunContinuationsAsynchronously));
test(new TaskCompletionSource<object>());
}
And here are the (annotated) results:
starting: RunContinuationsAsynchronously
waiting
-1 [11, Back, Pool] - before sleep 100
-1 [11, Back, Pool] - after sleep 100
-2 [11, Back, Pool] - before sleep 100
1 [12, Back, Pool] - before sleep 1000 --> continuations are correctly executed on different threads
2 [15, Back, Pool] - before sleep 300
3 [13, Back, Pool] - before sleep 300
-2 [11, Back, Pool] - after sleep 100 --> continuations correctly do not block the calling task/thread
3 [13, Back, Pool] - after sleep 300
2 [15, Back, Pool] - after sleep 300
1 [12, Back, Pool] - after sleep 1000
done
starting: None
-1 [13, Back, Pool] - before sleep 100
waiting
-1 [13, Back, Pool] - after sleep 100
-2 [13, Back, Pool] - before sleep 100
1 [15, Back, Pool] - before sleep 1000 --> continuations are incorrectly(?!) executed on different threads
2 [14, Back, Pool] - before sleep 300
3 [16, Back, Pool] - before sleep 300
-2 [13, Back, Pool] - after sleep 100
2 [14, Back, Pool] - after sleep 300 --> continuations incorrectly(?!) do not block the calling task/thread
3 [16, Back, Pool] - after sleep 300
1 [15, Back, Pool] - after sleep 1000
done
So, apparently, my 2nd assumption (without specifying the flag) was wrong: continuations are not run synchronously - at least not as I understand the term; they are executed on separate threads, so they do not block the calling thread or each other.
Why?