📎 Webclip
Asynchronous Programming
The page collects guidance for writing asynchronous code in .NET and ASP.NET Core. It emphasizes that async should propagate through the call stack, and it warns against blocking patterns, fire-and-forget mistakes, and misuse of task and cancellation APIs.
It also covers lower-level scenarios such as timer callbacks, concurrent dictionary caching, constructors, impersonation, and AsyncLocal usage, including cases where execution context capture can cause leaks or unexpected behavior.
Fichamento#
- Once a code path becomes async, the callers should also be async, because partial asynchrony can be worse than staying synchronous.
async voidis always bad in ASP.NET Core because exceptions can crash the process and the method cannot be tracked.- For precomputed or trivial results,
Task.FromResultis preferred overTask.Run;ValueTask<T>can avoid the allocation entirely. Task.Runshould not be used for long-running blocking work, because it steals a thread-pool thread; a dedicated thread orTaskCreationOptions.LongRunningis preferred.Task.ResultandTask.Waitshould generally be avoided because they cause sync over async, thread-pool starvation, and can deadlock in some application models.awaitis preferred overContinueWith, which behaves differently because it does not captureSynchronizationContext.TaskCompletionSource<T>should be created withTaskCreationOptions.RunContinuationsAsynchronouslyso continuations do not run inline on the thread that completes the task.CancellationTokenSourceinstances used for timeouts should be disposed so their timers do not stay in the queue.CancellationTokenvalues should be passed through to APIs that accept them so cancellation works across the whole call chain.- For uncancellable operations,
Task.WaitAsyncis preferred on .NET 6 or newer; older patterns useTask.WhenAny,CancellationTokenRegistration, or timeout tasks. StreamWriterandStreamshould be flushed asynchronously before disposal, or disposed withDisposeAsync, to avoid synchronous blocking on buffered output.- Returning a
Taskdirectly is faster, butasync/awaitnormalizes exceptions, improves diagnostics, and avoids leaking async locals out of the method. AsyncLocal<T>is powerful but risky because it flows through execution context; values should be immutable, thread-safe, and preferably non-disposable.- APIs such as
Timer,CancellationToken.Register,Task.Run, andThreadPool.QueueUserWorkItemcan capture execution context and keep async-local data alive longer than intended. - Using
CancellationToken.UnsafeRegisteravoids capturing execution context and can reduce memory leaks caused by async locals. - Setting
AsyncLocal<T>values outside async methods can let mutations propagate unexpectedly; async methods restore the original execution context on exit. Timercallbacks should not be implemented withasync voidor with blocking waits; the page shows discardedTaskcallbacks andPeriodicTimeras alternatives.ConcurrentDictionary.GetOrAddcan cause thread-pool starvation if the value factory blocks on async work; storing aTask<T>or anAsyncLazy<T>is preferred.- Constructors are synchronous, so asynchronous initialization should be moved to a factory method such as
CreateAsync. WindowsIdentity.RunImpersonatedAsyncis the recommended way to combine impersonation with asynchronous work in .NET 5 or newer.
