Minecraft-Console-Client/.skills/dotnet-performance-profiling-and-optimization/references/async-patterns.md
2026-06-25 23:02:43 +02:00

2.9 KiB

Async & Concurrency Patterns

Don't Expose Async Wrappers for Sync Methods

🟡 AVOID wrapping sync methods with Task.Run in libraries | .NET Core+

public Task<int> ComputeHashAsync(byte[] data) =>
    Task.Run(() => ComputeHash(data));

public int ComputeHash(byte[] data) { /* CPU-bound work */ }
// Consumer decides: var hash = await Task.Run(() => lib.ComputeHash(data));

Impact: Eliminates unnecessary thread pool queue/dequeue overhead per call.

Don't Expose Sync Wrappers for Async Methods

🟡 AVOID creating sync wrappers that block on async implementations | .NET Core+

public string GetData() => GetDataAsync().Result;

public async Task<string> GetDataAsync() { /* ... */ }

Impact: Prevents deadlocks and thread pool starvation from hidden sync-over-async blocking.

Use ValueTask for Hot Paths with Frequent Sync Completion

🟡 DO use ValueTask<T> on hot paths where sync completion is common | .NET Core 2.1+

public async Task<int> ReadAsync(Memory<byte> buffer)
{
    if (_bufferedCount > 0)
        return ReadFromBuffer(buffer.Span);
    return await ReadAsyncCore(buffer);
}

public ValueTask<int> ReadAsync(Memory<byte> buffer)
{
    if (_bufferedCount > 0)
        return new ValueTask<int>(ReadFromBuffer(buffer.Span));
    return new ValueTask<int>(ReadAsyncCore(buffer));
}

Impact: Eliminates Task<T> allocation on synchronous completion — the struct stores results inline.

Use Channels for Producer/Consumer

🟡 DO use System.Threading.Channels for producer-consumer patterns | .NET Core 3.0+

var queue = new BlockingCollection<WorkItem>();
var item = queue.Take();

var channel = Channel.CreateUnbounded<WorkItem>();

// Producer
await channel.Writer.WriteAsync(item);

// Consumer
await foreach (var item in channel.Reader.ReadAllAsync())
    Process(item);

Impact: ~25% faster, ~95% fewer GC collections vs manual approaches.

Avoid False Sharing with Thread-Local State

🟡 AVOID adjacent mutable fields written by different threads | .NET 7+

class SharedCounters
{
    public long Counter1;
    public long Counter2;
}

[StructLayout(LayoutKind.Explicit, Size = 128)]
struct PaddedCounter
{
    [FieldOffset(0)] public long Value;
}

Impact: Eliminates cross-core cache invalidation — can improve multi-threaded throughput by 10x+.

Detection

Scan recipes for async anti-patterns. Run these and report exact counts.

# async void methods (correctness issue — crashes on exception)
grep -rn --include='*.cs' 'async void' --exclude-dir=bin --exclude-dir=obj . | grep -v 'event' | wc -l

Patterns Requiring Manual Review

  • Sync-over-async (.Result, .Wait()): .Result matches any property named Result — needs type context to confirm it's Task.Result