Parallel Framework Extensions: Three Minor Things I Didn't Know This Morning
1. You Can Add a Continuation To A Completed Task
And the continuation will be run. Given the code:
var t1 = Task.Factory.StartNew(() => { /* Noop */ });
Thread.Sleep(100); // Ensure t1 has run
Trace.Assert(t1.Status == TaskStatus.RanToCompletion);
// Now t1 is completed... we can still add a continuation:
bool continued = false;
var t2 = t1.ContinueWith(t => { continued = true; });
t2.Wait();
Trace.Assert(continued);
Neither assert will fire. Thus the continue block has executed despite
being added to Task t1
after t1
had run.
This does of course prevent a race between TaskFactory.StartNew
returning a task that is scheduled (and therefore possibly already completed)
with a call to Task.ContinueWith
.
2. You Can Continue A Task Multiple Times
And the continuations run concurrently. I’m not sure that this would be useful very often (only if multiple tasks need to be started following a single initial task), but nice to know there is not an arbitrary zero or one multiplicity on continuations. Sample code:
var t1 = new Task(() => { Console.WriteLine(" This is the original task"); });
var t2 = t1.ContinueWith(t => {
Console.WriteLine(" This is the first continiation (before sleep: thread #{0})", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(250);
Console.WriteLine(" This is the first continiation (after sleep: thread #{0})", Thread.CurrentThread.ManagedThreadId);
});
var t3 = t1.ContinueWith(t => {
Console.WriteLine(" This is the second continiation (before sleep: thread #{0})", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(250);
Console.WriteLine(" This is the second continiation (after sleep: thread #{0})", Thread.CurrentThread.ManagedThreadId);
});
t1.Start();
Task.WaitAll(new[] { t1, t2, t3 });
Depending on the system this can give different results, but one should see the two “before sleep” messages before either of the two “after sleep” messages: both tasks are executing concurrently.
3. TaskFactory.FromAsync
Starts the Task
No illustration of this, it just doesn’t say so in the documentation.
I discovered this when a InvalidOperationException
was thrown for
starting an already complete task (only two statements later, the intermediate
one adding a continuation). To be fair to the designers, there is really no
reason to want to delay the execution of the task as the creation is a single
statement (and if you really needed to, just use a helper (anonymous) function).