dotnet-async
Skill1 starsChanged 27 days agoMITView on GitHub
C# async/await patterns advisor. Always use this skill when working with async/await in C#, Task, ValueTask, CancellationToken, ConfigureAwait, or deadlocks. Use when the user asks "why is my async code deadlocking", ".Result causing deadlock", "ConfigureAwait(false)", "async void vs async Task", "when to use ValueTask", "how do I cancel an async operation", "thread CancellationToken through async chain", or "can I call async from sync in C#". Read this skill before advising on any C# async or Task question.
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 1 is distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 25
If you genuinely need to call async from sync (e.g., a console app entry point or legacy code), use Task.Run(() => …
- Distinctive: line 119
Don't store it in a field and await it multiple times
- Very specific: line 120
Don't use it unless profiling shows Task allocation is a bottleneck.
- Very specific: line 124
Use Task.WhenAll for concurrent independent operations:
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
C# Async Patterns
C#'s async/await model is powerful but has a handful of well-known pitfalls. Most production async bugs fall into three categories: deadlocks from blocking on async code, unobserved exceptions from async void, and missing cancellation support. Know these cold.
Never block on async code
Calling .Result or .Wait() on a Task blocks the current thread and can deadlock in contexts with a synchronization context (WinForms, WPF, old ASP.NET):
// Bad: can deadlock in sync-context environments
var user = GetUserAsync(id).Result;
var data = FetchDataAsync().GetAwaiter().GetResult();
// Good: async all the way up
var user = await GetUserAsync(id);In ASP.NET Core there is no synchronization context, so .GetAwaiter().GetResult() won't deadlock there, but it still blocks a thread pool thread, defeating the purpose of async. Make it async all the way.
If you genuinely need to call async from sync (e.g., a console app entry point or legacy code), use Task.Run(() => method()).GetAwaiter().GetResult() as a last resort, but don't normalize it.
async void is dangerous
async void methods can't be awaited. If they throw, the exception is unobservable and crashes the process (or is silently swallowed depending on the host). Use async Task everywhere except event handlers.
// Bad: exception from SendEmailAsync is unobservable
async void NotifyUser(User user)
{
await SendEmailAsync(user.Email);
}
// Good: exception can be observed by the caller
async Task NotifyUserAsync(User user)
{
await SendEmailAsync(user.Email);
}The one legitimate use for async void is event handlers, where the delegate signature requires void:
button.Click += async (sender, e) =>
{
await DoWorkAsync();
};Even then, wrap the body in a try/catch to prevent unobserved exceptions from surfacing.
ConfigureAwait(false)
In library code, use ConfigureAwait(false) on every await to avoid capturing the synchronization context. This prevents deadlocks when library consumers call your code from a context-aware environment.
// Library code
public async Task<string> FetchAsync(string url)
{
var response = await httpClient.GetAsync(url).ConfigureAwait(false);
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}In application code (ASP.NET Core controllers, Blazor, etc.), ConfigureAwait(false) is not required: ASP.NET Core has no sync context. Omitting it is fine and reduces noise.
Rule of thumb: library authors need it; application developers usually don't.
CancellationToken: thread it through everything
Accept a CancellationToken in every async method and pass it to every downstream async call. This enables proper cooperative cancellation: the request is cancelled, the database query stops, the HTTP call aborts.
// Bad: no cancellation support — keeps running after the client disconnects
public async Task<User> GetUserAsync(string id)
{
return await db.Users.FindAsync(id);
}
// Good: propagates cancellation
public async Task<User> GetUserAsync(string id, CancellationToken ct = default)
{
return await db.Users.FindAsync(new object[] { id }, ct);
}In ASP.NET Core, inject CancellationToken as an action parameter; the framework provides a token that cancels when the client disconnects:
[HttpGet("{id}")]
public async Task<User> GetUser(string id, CancellationToken ct)
{
return await userService.GetUserAsync(id, ct);
}Task vs ValueTask
Task allocates a heap object. For hot paths where the result is often available synchronously (cache hits, simple property access), ValueTask avoids the allocation:
// ValueTask — no allocation when result is already available
public ValueTask<string> GetCachedAsync(string key)
{
if (_cache.TryGetValue(key, out var value))
return ValueTask.FromResult(value); // no allocation
return new ValueTask<string>(FetchFromDbAsync(key));
}ValueTask rules:
- Can only be awaited once
- Don't store it in a field and await it multiple times
- Don't use it unless profiling shows Task allocation is a bottleneck;
Taskis almost always fine
Concurrent operations
Use Task.WhenAll for concurrent independent operations:
// Sequential — total time = sum
var user = await GetUserAsync(id, ct);
var orders = await GetOrdersAsync(id, ct);
// Concurrent — total time = slowest
var (user, orders) = await (GetUserAsync(id, ct), GetOrdersAsync(id, ct));
// Or:
await Task.WhenAll(GetUserAsync(id, ct), GetOrdersAsync(id, ct));Task.WhenAll throws AggregateException if any task faults. To get partial results when some may fail:
var tasks = new[] { FetchA(ct), FetchB(ct), FetchC(ct) };
await Task.WhenAll(tasks); // awaiting after WhenAll unwraps the first exception
// Check each task individually for partial resultsAsync in constructors
Constructors can't be async. Use a static factory method:
public class Repository
{
private readonly DbConnection _connection;
private Repository(DbConnection connection) => _connection = connection;
public static async Task<Repository> CreateAsync(string connStr, CancellationToken ct = default)
{
var conn = new DbConnection(connStr);
await conn.OpenAsync(ct);
return new Repository(conn);
}
}
var repo = await Repository.CreateAsync(connStr, ct);What to watch for in code review
.Resultor.Wait(): deadlock risk and thread wasteasync voidoutside of event handlers: unobservable exceptions- Async methods that don't accept
CancellationToken awaitinside a loop whereTask.WhenAllwould work- Missing
ConfigureAwait(false)in library projects ValueTaskstored in a field or awaited multiple times
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- enigmalibs/ClaudeSkills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository25 other files it gives its agents.
- api-designSkill
- arch-reviewSkill
- db-reviewSkill
- debugSkill
- dotnet-opsSkill
- dotnet-testingSkill
- dotnet-typesSkill
- go-concurrencySkill
- go-opsSkill
- go-testingSkill
- go-typesSkill
- incidentSkill
- node-asyncSkill
- node-opsSkill
- perfSkill
- pr-prepSkill
- py-asyncSkill
- py-opsSkill
- py-testingSkill
- py-typesSkill
- refactorSkill
- securitySkill
- test-genSkill
- ts-testingSkill
- ts-typesSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

