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
async
keyword. - 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.Run
orTask.Factory.StartNew
.
Task Status
- Rarely used except for debugging, normally wait for the task to complete and extract the results.
Waiting
- All
Wait
operations 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). Wait
is 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, thenWait
raises anOperationCanceledException
. If a timeout occurs, thenWait
returns false. If the task completes in a failed or canceled state, thenWait
wraps any exceptions into anAggregateException
. Note that a canceled task will raise anOperationCanceledException
wrapped in anAggregateException
, whereas a canceled wait will raise an unwrappedOperationCanceledException
.WaitAll
andWaitAny
are similar, but for collections ofTask
.- Use
await
instead 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().GetResult
blocks 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,
await
should 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.StartNew
unless you are doing [dynamic task parallelism] (https://msdn.microsoft.com/en-us/library/ff963551.aspx) (which is very rare) - DO USE
Task.Run
. ItsCancellationToken
is mostly useless, but the other overloads are good for queueing work to the thread pool.
Starting Promise tasks
Task.Delay
is the async equivalent ofThread.Sleep
, use it in preference.Thread.Sleep
actually puts the current thread to sleep for a time (which means it is not doing any useful work).Task.Delay
allows the thread to be used to do other work (perhaps for other tasks).Task.FromResult
can be used to create a completed task with a result value.Task.FromException
andTask.FromCancelled
can be used to return completed tasks in those states (.Net 4.6 API).
Summary of Stephen Cleary’s async intro
- Avoid
async void
methods, except for event handlers. They can’t be awaited. - When you
await
a 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).