TAP stands for Task-based Asynchronous Pattern. Here’re a few tips how to use it effectively in some scenarios.
Long running operations
Task.Run for long running tasks.
Task.Factory.StartNew has an
TaskCreationOptions.LongRunning that under the covers creates a new
thread and returns a Task that represents the execution. Using this properly
requires several non-obvious parameters to be passed in to get the right
behavior on all platforms.
TaskCreationOptions.LongRunning with async code as this will create
a new thread which will be destroyed after first await
TaskCreationOptions.RunContinuationsAsynchronously. By default, Task
continuations will run inline on the same thread that calls
Try/Set(Result/Exception/Canceled). As a library author, this means having to
understand that calling code can resume directly on your thread. This is
extremely dangerous and can result in deadlocks, thread-pool starvation,
corruption of state (if code runs unexpectedly) and more.
Always dispose CancellationTokenSource(s) used for timeouts
Good implementation should dispose
Using a timeout
Good implementation cancels the timer if operation successfully completes.
Stream before calling
In some cases, we might be able to write methods that return a
await keywords. Such approach comes with some benefts and drawbacks.
Pros of eliding async
- (minimal) performance gain - without
await, the state machine does not need to be generated. An application will execute faster abd it will allocate less memory.
Pros of NOT eliding async
awaited call will throw both synchronous and asynchronous exceptions
Eliding causes only synchronous exceptions to be caught:
Asynchronous and synchronous exceptions are normalized to always be asynchronous.
Diagnostics of asynchronous methods is easier (full call stack).
AsyncLocalworks as expected (stephencleary.com)
Task.Factory.StartNew() under the hood with an option
DenyChildAttach. It means that it will ignore children tasks being attached.
If we want the attachment, we can use
Task will be completed only when its 2 children are finished.
Task.Run would complete before that, because it does not allow attachment.
Task.Run automatically unwraps the result of async operations inside.
Task.Factory.StartNew does not do that. Example:
The way around that is: