Task.ResultorTask.Waiton asynchronous operations is much worse than calling truly synchronous APIs. Here is what happens- An asynchronous operation is kicked off.
- The calling thread is blocked waiting for that operation to complete. When the asynchronous operation completes, it unblocks the code waiting on that operation. This takes place on another thread.
- This leads to thread-pool starvation and service outages due to 2 threads being used instead of 1 to complete synchronous operations.
- If a synchronization context is available it can even lead to deadlocks
- Is async all the way and embraces virality of the API where it makes sense (IO-bound path)
- Async void methods will crash the process if an exception is thrown
Task-returning methods are better since unhandled exceptions trigger theTaskScheduler.UnobservedTaskException- Be ware that only when the finalizers are run the unobserved exception is thrown
- Does not explicitely offload to the worker thread pool by using
Task.RunorTask.Factory.StartNew(offloading is a concern of the caller) - Returns
Task,Task<TResult>,ValueTaskorValueTask<TResult> - Accepts
CancellationTokenif cancellation is appropriate. It is OK to add it but not respect it. Cancellation is cooperative. - Tries to respect token where it can and makes sense
- Disposes owned
CancellationTokenSource(due to allocated Timer instances when used withCancelAfter) - Disposes owned
CancelationTokenRegistrationto not leak memory - For high-perf scenarios avoid closure capturing in token registrations
- For library or framework code uses
ConfigureAwait(false)to opt-out from context capturing if not needed - Can used linked token sources for internal SLAs and cancellation scenarios
- Favours simple sequential async execution over explicit concurrency (concurrency is hard)
- No silver bullet that magically makes your DB query fasters ;)
- For highperf scenario
asynckeyword can be omitted - Apply carefully and only after measuring
- For most scenarios apply the keyword since it prevents mistakes because
- Asynchronous and synchronous exceptions are normalized to always be asynchronous.
- The code is easier to modify (consider adding a using, for example).
- Diagnostics of asynchronous methods are easier (debugging hangs etc).
- Exceptions thrown will be automatically wrapped in the returned Task instead of surprising the caller with an actual exception.
- NET Core 2.2:
| Method | Allocated |
|---|---|
| Return | 376 B |
| Await | 488 B |
- NET Core 3.0 RC1:
| Method | Allocated |
|---|---|
| Return | 344 B |
| Await | 456 B |
- With .NET Core 2.1 and later finally readable stack traces
at StackTracesOhMy.Level6() in C:\p\Async.Netcore\StackTracesOhMy.cs:line 19
at StackTracesOhMyExtensions.Level5(StackTracesOhMy runnable) in C:\p\Async.Netcore\StackTracesOhMyExtensions.cs:line 41
at StackTracesOhMyExtensions.Level4(StackTracesOhMy runnable) in C:\p\Async.Netcore\StackTracesOhMyExtensions.cs:line 36
at StackTracesOhMyExtensions.Level3(StackTracesOhMy runnable) in C:\p\Async.Netcore\StackTracesOhMyExtensions.cs:line 31
at StackTracesOhMyExtensions.Level2(StackTracesOhMy runnable) in C:\p\Async.Netcore\StackTracesOhMyExtensions.cs:line 26
at StackTracesOhMyExtensions.Level2to6(StackTracesOhMy runnable) in C:\p\Async.Netcore\StackTracesOhMyExtensions.cs:line 21
Stream,Utf8JsonWriter,System.Threading.Timer,CancellationTokenRegistration,BinaryWriter,TextWriterandIAsyncEnumerator<T>implementIAsyncDisposableStream,BinaryWriter,TextWritercalls.DisposesynchronouslyStreamFlushAsynccallsFlushon another thread which is bad behavior that should be overwritten
IAsyncEnumerable<T>allows to write asynchronous pull based streams, similar to regular enumerables withyield returnandyield breakWithCancellationonly adds the token to the enumerator but doesn't influence the state machineWithCancellationin combination with[EnumeratorCancellation]can be used to create a combined token
WithCancellationinstructs the compiler generated statemachine to callGetAsyncEnumeratorwith the provided token. Otherwisedefault(CancellationToken)will be used.
- Async Enumerable can be combined in powerful ways with other async and TPL constructs such as
Task.WhenAny