Skip to main content

csharp-async-patterns

Use when C# asynchronous programming with async/await, Task, ValueTask, ConfigureAwait, and async streams for responsive applications.

Source facts

Repository
TheBushidoCollective/han
Last source activity
February 11, 2026 at 17:47
Detected SKILL.md language
English
Stars
198
Forks
22

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
csharp-async-patterns
user-invocable
false
description
Use when C# asynchronous programming with async/await, Task, ValueTask, ConfigureAwait, and async streams for responsive applications.
allowed-tools
["Read","Write","Edit","Grep","Glob","Bash"]
# C# Async Patterns Asynchronous programming in C# enables writing responsive applications that efficiently handle I/O-bound and CPU-bound operations without blocking threads. The async/await pattern provides a straightforward way to write asynchronous code that looks and behaves like synchronous code. ## Async/Await Basics The `async` and `await` keywords transform synchronous-looking code into state machines that handle asynchronous operations. ```csharp using System; using System.Net.Http; using System.Threading.Tasks; public class AsyncBasics { // Basic async method public async Task<string> FetchDataAsync(string url) { using var client = new HttpClient(); // await suspends execution until response received string content = await client.GetStringAsync(url); return content; } // Async method without return value public async Task ProcessDataAsync() { await Task.Delay(1000); // Simulate async work Console.WriteLine("Processing complete"); } // Multiple awaits public async Task<int> CalculateSumAsync() { int value1 = await GetValueAsync(1); int value2 = await GetValueAsync(2); int value3 = await GetValueAsync(3); return value1 + value2 + value3; } private async Task<int> GetValueAsync(int id) { await Task.Delay(100); return id * 10; } // Async with exception handling public async Task<string> SafeFetchAsync(string url) { try { using var client = new HttpClient(); return await client.GetStringAsync(url); } catch (HttpRequestException ex) { Console.WriteLine($"Request failed: {ex.Message}"); return string.Empty; } } // Calling async methods public async Task DemoAsync() { // Await the result string data = await FetchDataAsync("https://api.example.com"); // Fire and forget (not recommended) _ = ProcessDataAsync(); // Wait for completion await ProcessDataAsync(); } } ``` ## Task and Task\<T\> `Task` represents an asynchronous operation and provides methods for composition, continuation, and error handling. ```csharp using System; using System.Threading; using System.Threading.Tasks; public class TaskExamples { // Creating tasks public void CreateTasks() { // Task.Run for CPU-bound work Task<int> task1 = Task.Run(() => { Thread.Sleep(1000); return 42; }); // Task.FromResult for already-known values Task<int> task2 = Task.FromResult(100); // Task.CompletedTask for void operations Task task3 = Task.CompletedTask; // TaskCompletionSource for manual control var tcs = new TaskCompletionSource<string>(); Task<string> task4 = tcs.Task; tcs.SetResult("Done"); } // Task composition public async Task<string> ComposeTasks() { // Sequential execution int result1 = await Task1Async(); int result2 = await Task2Async(result1); // Parallel execution Task<int> t1 = Task1Async(); Task<int> t2 = Task2Async(10); await Task.WhenAll(t1, t2); return $"Results: {t1.Result}, {t2.Result}"; } // Task.WhenAll - wait for all tasks public async Task<int[]> WhenAllExample() { var tasks = new[] { Task.Run(() => ComputeValue(1)), Task.Run(() => ComputeValue(2)), Task.Run(() => ComputeValue(3)) }; int[] results = await Task.WhenAll(tasks); return results; } // Task.WhenAny - wait for first task public async Task<int> WhenAnyExample() { var task1 = DelayedValue(1000, 1); var task2 = DelayedValue(2000, 2); var task3 = DelayedValue(500, 3); Task<int> completed = await Task.WhenAny(task1, task2, task3); return await completed; } // Cancellation support public async Task<string> CancellableOperation( CancellationToken cancellationToken) { for (int i = 0; i < 10; i++) { cancellationToken.ThrowIfCancellationRequested(); await Task.Delay(100, cancellationToken); Console.WriteLine($"Step {i + 1}"); } return "Completed"; } // Helper methods private Task<int> Task1Async() => Task.FromResult(10); private Task<int> Task2Async(int value) => Task.FromResult(value * 2); private int ComputeValue(int x) => x * x; private async Task<int> DelayedValue(int delay, int value) { await Task.Delay(delay); return value; } } ``` ## ValueTask and ValueTask\<T\> `ValueTask` provides better performance for operations that often complete synchronously, avoiding heap allocations. ```csharp using System; using System.Threading.Tasks; public class ValueTaskExamples { private readonly Dictionary<string, string> _cache = new Dictionary<string, string>(); // ValueTask for cached operations public ValueTask<string> GetValueAsync(string key) { // Synchronous path - no allocation if (_cache.TryGetValue(key, out string? value)) { return new ValueTask<string>(value); } // Asynchronous path return new ValueTask<string>(FetchFromDatabaseAsync(key)); } private async Task<string> FetchFromDatabaseAsync(string key) { await Task.Delay(100); // Simulate database call string value = $"Value for {key}"; _cache[key] = value; return value; } // Converting between Task and ValueTask public async ValueTask<int> ConversionExample() { // ValueTask from Task Task<int> task = GetTaskAsync(); ValueTask<int> valueTask = new ValueTask<int>(task); return await valueTask; } private Task<int> GetTaskAsync() => Task.FromResult(42); // ValueTask best practices public async Task ValueTaskUsageAsync() { // Good: await immediately string value1 = await GetValueAsync("key1"); // Bad: storing ValueTask // ValueTask<string> vt = GetValueAsync("key2"); // await vt; // First await // await vt; // Second await - WRONG! // Good: convert to Task if needed multiple times Task<string> task = GetValueAsync("key2").AsTask(); await task; await task; // OK with Task } // ConfigureAwait with ValueTask public async ValueTask ConfigureAwaitExample() { // Don't capture context (for library code) string value = await GetValueAsync("key") .ConfigureAwait(false); Console.WriteLine(value); } } ``` ## ConfigureAwait `ConfigureAwait` controls whether to capture the synchronization context, critical for library code and avoiding deadlocks. ```csharp using System; using System.Threading.Tasks; public class ConfigureAwaitExamples { // Library method - use ConfigureAwait(false) public async Task<string> LibraryMethodAsync() { // Don't capture synchronization context await Task.Delay(100).ConfigureAwait(false); // This continues on thread pool thread string result = await GetDataAsync() .ConfigureAwait(false); return result.ToUpper(); } // UI method - use default (or ConfigureAwait(true)) public async Task UpdateUIAsync() { string data = await LoadDataAsync(); // This continues on UI thread // Can safely update UI controls Console.WriteLine($"Data: {data}"); } // Avoiding deadlocks public class DeadlockExample { // This can deadlock in synchronous context public string BadSync() { // DON'T DO THIS return GetDataAsync().Result; // Deadlock! } // Fix with ConfigureAwait(false) public string GoodSync() { return GetDataAsync() .ConfigureAwait(false) .GetAwaiter() .GetResult(); } // Better: make it async public async Task<string> BestAsync() { return await GetDataAsync(); } } // Mixing contexts public async Task MixedContextAsync() { // Runs on captured context await Task.Delay(100); Console.WriteLine("On original context"); // Runs on thread pool await Task.Delay(100).ConfigureAwait(false); Console.WriteLine("On thread pool"); // Still on thread pool (ConfigureAwait effect persists) await Task.Delay(100); Console.WriteLine("Still on thread pool"); } private async Task<string> GetDataAsync() { await Task.Delay(100); return "data"; } private async Task<string> LoadDataAsync() { await Task.Delay(100); return "loaded data"; } } ``` ## Async Streams (IAsyncEnumerable) Async streams enable asynchronous iteration over sequences of data, perfect for streaming APIs and large datasets. ```csharp using System; using System.Collections.Generic; using System.Net.Http; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; public class AsyncStreamExamples { // Basic async stream public async IAsyncEnumerable<int> GenerateNumbersAsync( int count) { for (int i = 0; i < count; i++) { await Task.Delay(100); yield return i; } } // Consuming async stream public async Task ConsumeStreamAsync() { await foreach (int number in GenerateNumbersAsync(10)) { Console.WriteLine(number); } } // Async stream with cancellation public async IAsyncEnumerable<string> ReadLinesAsync( string filePath, [EnumeratorCancellation] CancellationToken cancellationToken = default) { using var reader = new System.IO.StreamReader(filePath); while (!reader.EndOfStream) { cancellationToken.ThrowIfCancellationRequested(); string? line = await reader.ReadLineAsync(); if (line != null) { yield return line; } } } // Filtering async stream public async IAsyncEnumerable<int> FilterEvenNumbersAsync( IAsyncEnumerable<int> source) { await foreach (int number in source) { if (number % 2 == 0) { yield return number; } } } // Transforming async stream public async IAsyncEnumerable<string> FormatNumbersAsync( IAsyncEnumerable<int> source) { await foreach (int number in source) { yield return $"Number: {number:D3}"; } } // Async stream from API public async IAsyncEnumerable<string> FetchPagesAsync( string baseUrl, int totalPages) { using var client = new HttpClient(); for (int page = 1; page <= totalPages; page++) { string url = $"{baseUrl}?page={page}"; string content = await client.GetStringAsync(url); yield return content; } } // Composing async streams public async Task ComposeStreamsAsync() { var numbers = GenerateNumbersAsync(20); var evens = FilterEvenNumbersAsync(numbers); var formatted = FormatNumbersAsync(evens); await foreach (string value in formatted) { Console.WriteLine(value); } } } ``` ## Parallel Async Operations Combining parallelism with async operations for maximum throughput. ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; public class ParallelAsyncExamples { // Process items in parallel public async Task<List<string>> ProcessInParallelAsync( List<int> items) { var tasks = items.Select(async item => { await Task.Delay(100); return $"Processed {item}"; }); string[] results = await Task.WhenAll(tasks); return results.ToList(); } // Throttled parallel execution public async Task<List<string>> ThrottledParallelAsync( List<int> items, int maxConcurrency) { var semaphore = new SemaphoreSlim(maxConcurrency); var tasks = items.Select(async item => { await semaphore.WaitAsync(); try { await Task.Delay(100); return $"Processed {item}"; } finally { semaphore.Release(); } }); string[] results = await Task.WhenAll(tasks); return results.ToList(); } // Parallel.ForEachAsync (.NET 6+) public async Task ParallelForEachAsyncExample(List<int> items) { await Parallel.ForEachAsync( items, new ParallelOptions { MaxDegreeOfParallelism = 4 }, async (item, cancellationToken) => { await Task.Delay(100, cancellationToken); Console.WriteLine($"Processed {item}"); }); } // Batched processing public async Task<List<string>> BatchProcessAsync( List<int> items, int batchSize) { var results = new List<string>(); for (int i = 0; i < items.Count; i += batchSize) { var batch = items.Skip(i).Take(batchSize); var batchResults = await ProcessInParallelAsync( batch.ToList()); results.AddRange(batchResults); } return results; } } ``` ## Error Handling in Async Code Proper error handling is crucial for robust asynchronous applications. ```csharp using System; using System.Threading.Tasks; public class AsyncErrorHandling { // Basic try-catch public async Task<string> BasicErrorHandlingAsync() { try { return await RiskyOperationAsync(); } catch (InvalidOperationException ex) { Console.WriteLine($"Operation error: {ex.Message}"); return "default"; } catch (Exception ex) { Console.WriteLine($"Unexpected error: {ex.Message}"); throw; } } // AggregateException from WhenAll public async Task HandleMultipleErrorsAsync() { try { await Task.WhenAll( FailingTaskAsync("Task 1"), FailingTaskAsync("Task 2"), FailingTaskAsync("Task 3") ); } catch (Exception ex)
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub