Summary of Stephen Cleary’s ‘A Tour of Task’ articles
- Task as used by TPL
is completely different to Task as used by
async. - The vast majority of Task members have no place in async code.
- There are two types of Task ** Delegate tasks, which have code to run ** Promise tasks, which represent an event or signal (e.g. IO or timer based)
- Most TPL code uses Delegate tasks across multiple threads,, most async code uses Promise tasks which don’t tie up a thread.
Task Constructors
- These can only create Delegate tasks. Promise tasks are created using the
asynckeyword. - They create tasks that are not running. Don’t use them.
- Use Parallel or PLINQ for parallel code.
- For dynamic task parallelism, you can use
Task.RunorTask.Factory.StartNew.
Task Status
- Rarely used except for debugging, normally wait for the task to complete and extract the results.
Waiting
- All
Waitoperations block the calling thread until the task completes, so they are never used with Promise tasks. (Blocking on a Promise is a cause of deadlocks). Waitis rather simple: it will block the calling thread until the task completes, a timeout occurs, or the wait is cancelled. If the wait is cancelled, thenWaitraises anOperationCanceledException. If a timeout occurs, thenWaitreturns false. If the task completes in a failed or canceled state, thenWaitwraps any exceptions into anAggregateException. Note that a canceled task will raise anOperationCanceledExceptionwrapped in anAggregateException, whereas a canceled wait will raise an unwrappedOperationCanceledException.WaitAllandWaitAnyare similar, but for collections ofTask.- Use
awaitinstead ofWait.
Results
- Only exists on
Task<T>type. - Blocks like
Wait, with the same drawbacks. - Wraps any exceptions inside an
AggregateException, which makes error handling complicated. GetAwaiter().GetResultblocks likeResult, but does not wrap any exceptions. This works for bothTask<T>andTask. So if you have to block, this is better.- The vast majority of time,
awaitshould be used to get the result of a Promise task.
Continuations
- Don’t use
ContinueWith- the defaults are all wrong. Just useawait. - Don’t use
TaskFactory.ContinueWhenAny- useawait Task.WhenAny(...) - Don’t use
TaskFactory.ContinueWhenAll- useawait Task.WhenAll(...)
Starting Delegate tasks
The obsolete way and The correct way
- Don’t use
Start. - Don’t use
RunSynchronously. It runs on the current thread. - Don’t use
Task.Factory.StartNewunless you are doing [dynamic task parallelism] (https://msdn.microsoft.com/en-us/library/ff963551.aspx) (which is very rare) - DO USE
Task.Run. ItsCancellationTokenis mostly useless, but the other overloads are good for queueing work to the thread pool.
Starting Promise tasks
Task.Delayis the async equivalent ofThread.Sleep, use it in preference.Thread.Sleepactually puts the current thread to sleep for a time (which means it is not doing any useful work).Task.Delayallows the thread to be used to do other work (perhaps for other tasks).Task.FromResultcan be used to create a completed task with a result value.Task.FromExceptionandTask.FromCancelledcan be used to return completed tasks in those states (.Net 4.6 API).
Summary of Stephen Cleary’s async intro
- Avoid
async voidmethods, except for event handlers. They can’t be awaited. - When you
awaita built-in awaitable, the current “context” is captured and later re-applied when the rest of the async method is executed. Context means UI context, ASP.Net request context, or thread-pool context. - The context overhead can be avoided by doing
await FooAsync(parms).ConfigureAwait(false)(you should do this by default unless you know you need the original context).