From dotnet
Best practices and patterns for C# async/await programming including Task and ValueTask usage, CancellationToken propagation, IAsyncDisposable and await using, ConfigureAwait, async streams (IAsyncEnumerable), parallel task execution, and common pitfalls like deadlocks and async void. Use this skill whenever writing, reviewing, or refactoring asynchronous C# code — including when the user mentions async methods, Task-returning APIs, cancellation, deadlocks, blocking on async code, or resource disposal in async contexts, even if they do not explicitly say "async best practices."
npx claudepluginhub atc-net/atc-agentic-toolkit --plugin dotnetThis skill uses the workspace's default tool permissions.
Apply these practices when writing, reviewing, or refactoring asynchronous C# code.
Guides C# async/await patterns including Task, ValueTask, async streams, and cancellation for responsive applications. Use when writing asynchronous C# code.
Writing async/await code. Task patterns, ConfigureAwait, cancellation, and common agent pitfalls.
Provides .NET async patterns using Task, ValueTask, ConfigureAwait, IAsyncEnumerable for non-blocking I/O, streams, cancellation, concurrency control, and anti-patterns.
Share bugs, ideas, or general feedback.
Apply these practices when writing, reviewing, or refactoring asynchronous C# code.
Async (e.g., GetDataAsync for a synchronous GetData)Task<T> when the method produces a valueTask when the method has no return valueValueTask<T> when the method frequently completes synchronously (e.g., cache hits, short-circuit returns) — this avoids a Task heap allocation on the hot pathasync void except in event handlers — async void methods swallow exceptions and cannot be awaited, making errors invisible to the callerCancellationTokens are the cooperative cancellation mechanism in .NET. Dropping a token silently means the user loses the ability to cancel an operation that might be long-running or expensive.
CancellationToken as the last parameter in every async method signatureHttpClient, Stream, DbCommand, Channel, EF Core queries, etc.)token.ThrowIfCancellationRequested() inside long-running CPU loops to make them responsive to cancellationOperationCanceledException at boundary layers (API controllers, hosted service entry points) — do not swallow it or rethrow as a different exception typeCancellationTokenSource.CreateLinkedTokenSource() to combine scopes — for instance, merging a request-scoped token with a user-initiated cancel:using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(requestToken, userCancelToken);
await ProcessAsync(linkedCts.Token);
await expressions in try/catch to handle faulted tasksTask.FromException<T>() (or ValueTask.FromException) to return a pre-faulted task instead of throwing before the first await in a task-returning method — this keeps the caller's exception-handling path consistentConfigureAwait(false) after every await to avoid capturing the synchronization context (see ConfigureAwait section below)The ConfigureAwait decision depends on where your code runs:
.ConfigureAwait(false) on every await. Library code should not assume a synchronization context exists, and capturing one unnecessarily can cause deadlocks when callers block with .Result or .Wait().ConfigureAwait(false). ASP.NET Core has no SynchronizationContext, so it is a no-op there, but in UI frameworks (WPF, WinForms, MAUI) the default behavior of resuming on the UI thread is what you want.Task.WhenAll() to run independent tasks concurrently and await all of themTask.WhenAny() for racing tasks (e.g., timeout patterns, first-response-wins)async/await when the method simply returns another task — just return the Task directly (but keep async if you need try/catch or using around the await)Task.Run() only to offload CPU-bound work to the thread pool — never wrap purely async I/O in Task.Run()Use IAsyncEnumerable<T> to produce or consume sequences of data asynchronously (e.g., database cursor results, paginated API calls, streaming file reads):
async IAsyncEnumerable<Item> GetItemsAsync(
[EnumeratorCancellation] CancellationToken ct = default)
{
await foreach (var row in db.QueryAsync(ct))
{
yield return Map(row);
}
}
[EnumeratorCancellation] to the CancellationToken parameter so callers can pass a token via WithCancellation()await foreach to consume async streamsWhen a resource performs I/O during cleanup (flushing buffers, closing network connections), use async disposal to avoid blocking a thread:
IAsyncDisposable on classes that hold async resources (database connections, HTTP clients, streams)await using over using whenever the type implements IAsyncDisposable:await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync(cancellationToken);
IDisposable and IAsyncDisposable, await using will call DisposeAsync() — which is the one you want in async code pathsStandard locks (lock, Monitor) cannot be held across an await because the continuation may run on a different thread. Use async-aware synchronization primitives instead:
SemaphoreSlim (with await semaphore.WaitAsync(ct)) as an async-compatible mutex or throttleChannel<T> for async producer-consumer patterns — it is allocation-efficient and back-pressure-aware| Anti-pattern | Why it is dangerous |
|---|---|
.Wait(), .Result, .GetAwaiter().GetResult() | Blocks the calling thread and can deadlock when a SynchronizationContext exists. Use await instead. |
async void methods (non-event-handler) | Exceptions go unobserved and crash the process. Return Task so the caller can await and catch. |
| Mixing sync and async | Calling async from sync (or vice versa) introduces deadlock risk and thread-pool starvation. Keep the call chain consistently async. |
| Forgetting to await a Task | The task runs fire-and-forget; exceptions are silently lost and execution order becomes unpredictable. |
Task.Run() wrapping async I/O | Wastes a thread-pool thread that just waits on I/O. Call the async method directly. |
Task/Task<T> and accept a CancellationTokenCreateAsync() method rather than doing async work in the constructorWhen reviewing C# code, identify async anti-patterns from this guide and suggest concrete improvements with corrected code.