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-async3description: 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<!-- Generated from harness/github-copilot/skills/csharp-async/SKILL.md by harness/claude-code/scripts/convert_from_copilot.py. Edit the source, not this file. -->78# C# async programming best practices910Apply 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.1112## When to invoke1314- "Review this C# async code for best practices."15- "Fix .Wait() or .Result deadlocks in this method."16- "Should this return Task, Task<T>, ValueTask<T>, or async void?"17- "Add cancellation and Task.WhenAll to this async workflow."1819## API shape rules2021| Concern | Rule | Example |22| --- | --- | --- |23| Naming | Add `Async` suffix to async methods and match synchronous counterparts when applicable. | `GetData()` → `GetDataAsync()` |24| Returns a value | Return `Task<T>` by default. | `Task<Customer>` |25| No value | Return `Task`. | `Task SaveAsync(...)` |26| 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>` |27| Event handler | `async void` is allowed only for event handlers; plain `void` async APIs are otherwise forbidden. | `async void Button_Click(...)` |28| Public API style | Follow the task-based asynchronous pattern (TAP). | Accept `CancellationToken` for cancellable work. |2930## Exception, cancellation, and context rules3132- 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.33- Do not swallow exceptions; rethrow with `throw;` or return a meaningful failed `Task`.34- Use `Task.FromException()` when constructing an already-faulted `Task` result without running an async state machine.35- Use `CancellationToken` for long-running operations and pass it through to I/O APIs.36- 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.3738## Concurrency patterns3940| Pattern | Use when | Avoid when |41| --- | --- | --- |42| `await` sequentially | Later work depends on earlier results. | Independent I/O could run in parallel. |43| `Task.WhenAll()` | Multiple independent operations should run concurrently and all must complete. | Operations must be throttled or ordered. |44| `Task.WhenAny()` | Implement timeout, fallback, or first-success behavior. | You would abandon tasks without cancellation/observation. |45| 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. |46| `IAsyncEnumerable<T>` | Stream asynchronous sequences without buffering all results. | Consumers require a materialized list and data size is small. |4748## Common pitfalls4950| Pitfall | Why it is wrong | Fix |51| --- | --- | --- |52| `.Wait()`, `.Result`, `.GetAwaiter().GetResult()` | Blocks threads and can deadlock under synchronization contexts. | Make the call chain async and `await`. |53| Mixing blocking and async code | Wastes thread-pool threads and hides deadlocks. | Use async I/O end to end. |54| Unnecessary `async` / `await` | Adds a state machine without benefit. | Return the existing `Task` directly when safe. |55| Fire-and-forget `Task` | Exceptions can be lost and lifetime is unclear. | Await, return, or route to a supervised background service. |56| Missing await | Work may run after the caller thinks it completed. | Always await or intentionally capture and observe the `Task`. |5758## Output template5960````markdown61## C# async review - <file or API>6263**Status:** pass | fixes recommended | fixed | blocked6465| Finding | Evidence | Recommendation |66| --- | --- | --- |67| `<deadlock/naming/return/cancellation/concurrency issue>` | `<code reference>` | `<specific async pattern>` |6869### Suggested shape70```csharp71<corrected signature or representative snippet>72```7374### Validation75- Build/tests: `<command and result or not run>`76````7778## Quality gate7980- [ ] Async methods use the `Async` suffix unless they are event handlers or framework-mandated names.81- [ ] Return types are `Task<T>`, `Task`, justified `ValueTask<T>`, or event-handler-only `async void`.82- [ ] No `.Wait()`, `.Result`, or `.GetAwaiter().GetResult()` remains in async call paths without a documented boundary reason.83- [ ] Long-running or I/O-bound APIs accept and propagate `CancellationToken` where appropriate.84- [ ] Independent operations use `Task.WhenAll()` or `Task.WhenAny()` only when lifetime and exception handling are correct.85- [ ] Library code uses `ConfigureAwait(false)` where context capture is unnecessary.