C# async programming best practices
Apply Task-based asynchronous pattern rules to C# code so async APIs are correctly named, awaitable, cancellable, exception-safe, and free of sync-over-async deadlocks in async/await code.
When to invoke
- "Review this C# async code for best practices."
- "Fix .Wait() or .Result deadlocks in this method."
- "Should this return Task, Task, ValueTask, or async void?"
- "Add cancellation and Task.WhenAll to this async workflow."
API shape rules
| Concern |
Rule |
Example |
| Naming |
Add Async suffix to async methods and match synchronous counterparts when applicable. |
GetData() → GetDataAsync() |
| Returns a value |
Return Task<T> by default. |
Task<Customer> |
| No value |
Return Task. |
Task SaveAsync(...) |
| High-performance cached or frequently synchronous result |
Consider ValueTask<T> only when allocation reduction is measured and callers can obey its constraints; this is a high-performance exception, not the default. |
ValueTask<int> |
| Event handler |
async void is allowed only for event handlers; plain void async APIs are otherwise forbidden. |
async void Button_Click(...) |
| Public API style |
Follow the task-based asynchronous pattern (TAP). |
Accept CancellationToken for cancellable work. |
Exception, cancellation, and context rules
- Use
try/catch around awaited operations when you can add context, translate known exceptions, or perform cleanup; keep this as the explicit try/catch rule.
- Do not swallow exceptions; rethrow with
throw; or return a meaningful failed Task.
- Use
Task.FromException() when constructing an already-faulted Task result without running an async state machine.
- Use
CancellationToken for long-running operations and pass it through to I/O APIs.
- Use
ConfigureAwait(false) in library code that does not need a captured synchronization context; avoid applying it blindly in app/UI code where context may be required.
Concurrency patterns
| Pattern |
Use when |
Avoid when |
await sequentially |
Later work depends on earlier results. |
Independent I/O could run in parallel. |
Task.WhenAll() |
Multiple independent operations should run concurrently and all must complete. |
Operations must be throttled or ordered. |
Task.WhenAny() |
Implement timeout, fallback, or first-success behavior. |
You would abandon tasks without cancellation/observation. |
| Pass through task |
The method only returns another task with no cleanup, try/catch, or transformation. |
You need using, finally, exception context, or post-processing. |
IAsyncEnumerable<T> |
Stream asynchronous sequences without buffering all results. |
Consumers require a materialized list and data size is small. |
Common pitfalls
| Pitfall |
Why it is wrong |
Fix |
.Wait(), .Result, .GetAwaiter().GetResult() |
Blocks threads and can deadlock under synchronization contexts. |
Make the call chain async and await. |
| Mixing blocking and async code |
Wastes thread-pool threads and hides deadlocks. |
Use async I/O end to end. |
Unnecessary async / await |
Adds a state machine without benefit. |
Return the existing Task directly when safe. |
Fire-and-forget Task |
Exceptions can be lost and lifetime is unclear. |
Await, return, or route to a supervised background service. |
| Missing await |
Work may run after the caller thinks it completed. |
Always await or intentionally capture and observe the Task. |
Output template
## C# async review - <file or API>
**Status:** pass | fixes recommended | fixed | blocked
| Finding | Evidence | Recommendation |
| --- | --- | --- |
| `<deadlock/naming/return/cancellation/concurrency issue>` | `<code reference>` | `<specific async pattern>` |
### Suggested shape
```csharp
<corrected signature or representative snippet>
```
### Validation
- Build/tests: `<command and result or not run>`
Quality gate
1---2name: csharp-async-23description: Review, design, and fix C# async code using Task, Task<T>, ValueTask<T>, cancellation, ConfigureAwait, async streams, and TAP conventions. Use when the user asks for C# async best practices, deadlock fixes, async method naming, parallel awaits, or replacing .Wait(), .Result, and async void.4---56# C# async programming best practices78Apply Task-based asynchronous pattern rules to C# code so async APIs are correctly named, awaitable, cancellable, exception-safe, and free of sync-over-async deadlocks in async/await code.910## When to invoke1112- "Review this C# async code for best practices."13- "Fix .Wait() or .Result deadlocks in this method."14- "Should this return Task, Task<T>, ValueTask<T>, or async void?"15- "Add cancellation and Task.WhenAll to this async workflow."1617## API shape rules1819| Concern | Rule | Example |20| --- | --- | --- |21| Naming | Add `Async` suffix to async methods and match synchronous counterparts when applicable. | `GetData()` → `GetDataAsync()` |22| Returns a value | Return `Task<T>` by default. | `Task<Customer>` |23| No value | Return `Task`. | `Task SaveAsync(...)` |24| High-performance cached or frequently synchronous result | Consider `ValueTask<T>` only when allocation reduction is measured and callers can obey its constraints; this is a high-performance exception, not the default. | `ValueTask<int>` |25| Event handler | `async void` is allowed only for event handlers; plain `void` async APIs are otherwise forbidden. | `async void Button_Click(...)` |26| Public API style | Follow the task-based asynchronous pattern (TAP). | Accept `CancellationToken` for cancellable work. |2728## Exception, cancellation, and context rules2930- Use `try`/`catch` around awaited operations when you can add context, translate known exceptions, or perform cleanup; keep this as the explicit try/catch rule.31- Do not swallow exceptions; rethrow with `throw;` or return a meaningful failed `Task`.32- Use `Task.FromException()` when constructing an already-faulted `Task` result without running an async state machine.33- Use `CancellationToken` for long-running operations and pass it through to I/O APIs.34- Use `ConfigureAwait(false)` in library code that does not need a captured synchronization context; avoid applying it blindly in app/UI code where context may be required.3536## Concurrency patterns3738| Pattern | Use when | Avoid when |39| --- | --- | --- |40| `await` sequentially | Later work depends on earlier results. | Independent I/O could run in parallel. |41| `Task.WhenAll()` | Multiple independent operations should run concurrently and all must complete. | Operations must be throttled or ordered. |42| `Task.WhenAny()` | Implement timeout, fallback, or first-success behavior. | You would abandon tasks without cancellation/observation. |43| Pass through task | The method only returns another task with no cleanup, `try`/`catch`, or transformation. | You need `using`, `finally`, exception context, or post-processing. |44| `IAsyncEnumerable<T>` | Stream asynchronous sequences without buffering all results. | Consumers require a materialized list and data size is small. |4546## Common pitfalls4748| Pitfall | Why it is wrong | Fix |49| --- | --- | --- |50| `.Wait()`, `.Result`, `.GetAwaiter().GetResult()` | Blocks threads and can deadlock under synchronization contexts. | Make the call chain async and `await`. |51| Mixing blocking and async code | Wastes thread-pool threads and hides deadlocks. | Use async I/O end to end. |52| Unnecessary `async` / `await` | Adds a state machine without benefit. | Return the existing `Task` directly when safe. |53| Fire-and-forget `Task` | Exceptions can be lost and lifetime is unclear. | Await, return, or route to a supervised background service. |54| Missing await | Work may run after the caller thinks it completed. | Always await or intentionally capture and observe the `Task`. |5556## Output template5758````markdown59## C# async review - <file or API>6061**Status:** pass | fixes recommended | fixed | blocked6263| Finding | Evidence | Recommendation |64| --- | --- | --- |65| `<deadlock/naming/return/cancellation/concurrency issue>` | `<code reference>` | `<specific async pattern>` |6667### Suggested shape68```csharp69<corrected signature or representative snippet>70```7172### Validation73- Build/tests: `<command and result or not run>`74````7576## Quality gate7778- [ ] Async methods use the `Async` suffix unless they are event handlers or framework-mandated names.79- [ ] Return types are `Task<T>`, `Task`, justified `ValueTask<T>`, or event-handler-only `async void`.80- [ ] No `.Wait()`, `.Result`, or `.GetAwaiter().GetResult()` remains in async call paths without a documented boundary reason.81- [ ] Long-running or I/O-bound APIs accept and propagate `CancellationToken` where appropriate.82- [ ] Independent operations use `Task.WhenAll()` or `Task.WhenAny()` only when lifetime and exception handling are correct.83- [ ] Library code uses `ConfigureAwait(false)` where context capture is unnecessary.