본문으로 건너뛰기
cccskills
무료GitHub에 공개

dotnet-csharp-async-patterns

Writing async/await code. Task patterns, ConfigureAwait, cancellation, and common agent pitfalls.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md11.2 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

dotnet-csharp-async-patterns

Async/await best practices for .NET applications. Covers correct task usage, cancellation propagation, and the most common mistakes AI agents make when generating async code.

Scope

  • Async/await best practices and Task patterns
  • ConfigureAwait usage and SynchronizationContext
  • Cancellation token propagation
  • Common async agent pitfalls and fixes

Out of scope

  • Thread synchronization primitives (lock, SemaphoreSlim) -- see [skill:dotnet-csharp-concurrency-patterns]
  • Channel<T> producer/consumer patterns -- see [skill:dotnet-channels]
  • BackgroundService registration and lifecycle -- see [skill:dotnet-background-services]

Cross-references: [skill:dotnet-csharp-dependency-injection] for IHostedService/BackgroundService registration, [skill:dotnet-csharp-coding-standards] for Async suffix naming, [skill:dotnet-csharp-modern-patterns] for language-level features.


Core Rules

Always Async All the Way

Every method in the async call chain must be async and awaited. Mixing sync and async causes deadlocks or thread pool starvation.


// Correct: async all the way
public async Task<Order> GetOrderAsync(int id, CancellationToken ct = default)
{
    var order = await _repo.GetByIdAsync(id, ct);
    return order;
}

// WRONG: blocking on async -- causes deadlocks in ASP.NET and UI contexts
public Order GetOrder(int id)
{
    return _repo.GetByIdAsync(id).Result; // DEADLOCK RISK
}

```text

### Prefer `Task` and `ValueTask`

Return `Task` or `Task<T>` by default. Use `ValueTask<T>` when the method frequently completes synchronously (cache
hits, buffered I/O) to avoid `Task` allocation.

```csharp

// ValueTask: frequently synchronous completion
public ValueTask<User?> GetCachedUserAsync(int id, CancellationToken ct = default)
{
    if (_cache.TryGetValue(id, out var user))
    {
        return ValueTask.FromResult<User?>(user);
    }

    return LoadUserAsync(id, ct);
}

private async ValueTask<User?> LoadUserAsync(int id, CancellationToken ct)
{
    var user = await _repo.GetByIdAsync(id, ct);
    if (user is not null)
    {
        _cache[id] = user;
    }

    return user;
}

```text

**ValueTask rules:**

- Never `await` a `ValueTask` more than once
- Never use `.Result` or `.GetAwaiter().GetResult()` on an incomplete `ValueTask`
- If you need to await multiple times or pass it around, convert with `.AsTask()`

---

## Agent Gotchas

These are the most common async mistakes AI agents make when generating C# code.

### 1. Blocking on Async (`.Result`, `.Wait()`, `.GetAwaiter().GetResult()`)

```csharp

// WRONG -- all of these can deadlock
var result = GetDataAsync().Result;
GetDataAsync().Wait();
var result = GetDataAsync().GetAwaiter().GetResult();

// CORRECT
var result = await GetDataAsync();

```text

The only safe place for `.GetAwaiter().GetResult()` is in `Main()` pre-C# 7.1 or in rare infrastructure code where async
is impossible (static constructors, `Dispose()`).

### 2. `async void`

`async void` methods cannot be awaited, and unhandled exceptions in them crash the process.

```csharp

// WRONG -- fire-and-forget, unobserved exceptions
async void ProcessOrder(Order order)
{
    await _repo.SaveAsync(order);
}

// CORRECT
async Task ProcessOrderAsync(Order order)
{
    await _repo.SaveAsync(order);
}

```text

The **only** valid use of `async void` is event handlers (WinForms, WPF, Blazor `@onclick`), where the framework
requires a `void` return type.

### 3. Missing `ConfigureAwait`

In **library code**, use `ConfigureAwait(false)` to avoid capturing the synchronization context. In **application code**
(ASP.NET Core, console apps), it is not needed because there is no synchronization context.

```csharp

// Library code
public async Task<byte[]> ReadFileAsync(string path, CancellationToken ct = default)
{
    var bytes = await File.ReadAllBytesAsync(path, ct).ConfigureAwait(false);
    return bytes;
}

// Application code (ASP.NET Core) -- ConfigureAwait not needed
public async Task<IActionResult> GetOrder(int id, CancellationToken ct)
{
    var order = await _service.GetOrderAsync(id, ct);
    return Ok(order);
}

```text

### 4. Fire-and-Forget Without Error Handling

```csharp

// WRONG -- exception is silently swallowed
_ = SendEmailAsync(order);

// CORRECT -- use IHostedService or a background channel
await _backgroundQueue.EnqueueAsync(ct => SendEmailAsync(order, ct));

```text

If fire-and-forget is truly necessary, at minimum log the exception:

```csharp

_ = Task.Run(async () =>
{
    try
    {
        await SendEmailAsync(order);
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Failed to send email for order {OrderId}", order.Id);
    }
});

```text

### 5. Forgetting `CancellationToken`

Always accept and forward `CancellationToken`. Never silently drop it.

```csharp

// WRONG -- token not forwarded
public async Task<List<Order>> GetAllAsync(CancellationToken ct = default)
{
    return await _dbContext.Orders.ToListAsync(); // missing ct!
}

// CORRECT
public async Task<List<Order>> GetAllAsync(CancellationToken ct = default)
{
    return await _dbContext.Orders.ToListAsync(ct);
}

```text

---

## Cancellation Patterns

### Creating Linked Tokens

Combine external cancellation with a timeout:

```csharp

public async Task<Result> ProcessWithTimeoutAsync(CancellationToken ct = default)
{
    using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
    cts.CancelAfter(TimeSpan.FromSeconds(30));

    return await DoWorkAsync(cts.Token);
}

```text

### Responding to Cancellation

```csharp

public async Task ProcessBatchAsync(IEnumerable<Item> items, CancellationToken ct = default)
{
    foreach (var item in items)
    {
        ct.ThrowIfCancellationRequested();
        await ProcessItemAsync(item, ct);
    }
}

```text

---

## Parallel Async

### `Task.WhenAll` for Independent Operations

```csharp

public async Task<Dashboard> LoadDashboardAsync(int userId, CancellationToken ct = default)
{
    var ordersTask = _orderService.GetRecentAsync(userId, ct);
    var profileTask = _profileService.GetAsync(userId, ct);
    var statsTask = _statsService.GetAsync(userId, ct);

    await Task.WhenAll(ordersTask, profileTask, statsTask);

    return new Dashboard(ordersTask.Result, profileTask.Result, statsTask.Result);
}

```text

### `Parallel.ForEachAsync` (.NET 6+) for Bounded Parallelism

```csharp

await Parallel.ForEachAsync(items, new ParallelOptions
{
    MaxDegreeOfParallelism = 4,
    CancellationToken = ct
}, async (item, token) =>
{
    await ProcessItemAsync(item, token);
});

```text

---

## `IAsyncEnumerable<T>` Streaming

Use `IAsyncEnumerable<T>` for streaming results instead of buffering entire collections:

```csharp

public async IAsyncEnumerable<Order> GetOrdersStreamAsync(
    [EnumeratorCancellation] CancellationToken ct = default)
{
    await foreach (var order in _dbContext.Orders.AsAsyncEnumerable().WithCancellation(ct))
    {
        yield return order;
    }
}

```text

---

## Background Work

For background processing, use `BackgroundService` (or `IHostedService`) instead of `Task.Run` or fire-and-forget
patterns. See [skill:dotnet-csharp-dependency-injection] for registration patterns.

```csharp

public sealed class OrderProcessorWorker(
    IServiceScopeFactory scopeFactory,
    ILogger<OrderProcessorWorker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using var scope = scopeFactory.CreateScope();
            var processor = scope.ServiceProvider.GetRequiredService<IOrderProcessor>();

            await processor.ProcessPendingAsync(stoppingToken);
            await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
        }
    }
}

```text

---

## Testing Async Code

```csharp

[Fact]
public async Task GetOrderAsync_WhenFound_ReturnsOrder()
{
    // Arrange
    var repo = Substitute.For<IOrderRepository>();
    repo.GetByIdAsync(42, Arg.Any<CancellationToken>())
        .Returns(new Order { Id = 42 });
    var service = new OrderService(repo);

    // Act
    var result = await service.GetOrderAsync(42);

    // Assert
    Assert.NotNull(result);
    Assert.Equal(42, result.Id);
}

[Fact]
public async Task ProcessAsync_WhenCancelled_ThrowsOperationCanceled()
{
    using var cts = new CancellationTokenSource();
    cts.Cancel();

    await Assert.ThrowsAsync<OperationCanceledException>(
        () => _service.ProcessAsync(cts.Token));
}

```text

---

## Knowledge Sources

Async patterns in this skill are grounded in publicly available content from:

- **Stephen Cleary's "Concurrency in C#" and Blog** -- Definitive async best practices for .NET. Key guidance applied in
  this skill: "async all the way" (never block on async), "there is no thread" (async I/O does not consume a thread
  while waiting), correct CancellationToken propagation, async disposal via IAsyncDisposable, and BackgroundService
  patterns for long-running work. Source: https://blog.stephencleary.com/
- **David Fowler's Async Guidance** -- Practical async anti-patterns and diagnostic scenarios for ASP.NET Core. Source:
  https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md
- **Stephen Toub's ConfigureAwait FAQ** -- Canonical reference for ConfigureAwait behavior across application types.
  Source: https://devblogs.microsoft.com/dotnet/configureawait-faq/

> **Note:** This skill applies publicly documented guidance. It does not represent or speak for the named sources.



## Code Navigation (Serena MCP)

**Primary approach:** Use Serena symbol operations for efficient code navigation:

1. **Find definitions**: `serena_find_symbol` instead of text search
2. **Understand structure**: `serena_get_symbols_overview` for file organization
3. **Track references**: `serena_find_referencing_symbols` for impact analysis
4. **Precise edits**: `serena_replace_symbol_body` for clean modifications

**When to use Serena vs traditional tools:**
- ✅ **Use Serena**: Navigation, refactoring, dependency analysis, precise edits
- ✅ **Use Read/Grep**: Reading full files, pattern matching, simple text operations
- ✅ **Fallback**: If Serena unavailable, traditional tools work fine

**Example workflow:**
```text
# Instead of:
Read: src/Services/OrderService.cs
Grep: "public void ProcessOrder"

# Use:
serena_find_symbol: "OrderService/ProcessOrder"
serena_get_symbols_overview: "src/Services/OrderService.cs"
```
## References

- [Async/await best practices (David Fowler)](https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md)
- [Stephen Cleary's Async Blog](https://blog.stephencleary.com/)
- [Asynchronous programming patterns](https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/)
- [Task-based asynchronous pattern (TAP)](https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap)
- [ConfigureAwait FAQ](https://devblogs.microsoft.com/dotnet/configureawait-faq/)
- [Framework Design Guidelines](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/)

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Patterns and techniques for evaluating and improving AI agent outputs.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rudironsoni/Synaxis22026년 3월 17일 업데이트

Comprehensive AI prompt engineering safety review and improvement prompt. Analyzes prompts for safety, bias, security vulnerabilities, and effectiveness while providing detailed improvement recommendations.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rudironsoni/Synaxis22026년 3월 17일 업데이트

Use when user requests research requiring multiple sources, comprehensive analysis, or synthesis across topics - technical research, domain knowledge gathering, market analysis, or learning about complex subjects

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rudironsoni/Synaxis22026년 3월 17일 업데이트

deep-wiki

무료

AI-powered wiki generation for code repositories with commands, agents, and skills

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rudironsoni/Synaxis22026년 3월 17일 업데이트

Use when building .NET 10 or C# 14 applications; when using minimal APIs, modular monolith patterns, or feature folders; when implementing HTTP resilience, Options pattern, Channels, or validation; when seeing outdated patterns like old extension method syntax

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rudironsoni/Synaxis22026년 3월 17일 업데이트

Implements accessible .NET UI. SemanticProperties, ARIA, AutomationPeer, testing per platform.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rudironsoni/Synaxis22026년 3월 17일 업데이트

rudironsoni의 스킬 모두 보기

이 스킬의 문제 신고