### Asynchronous Service Initialization - Good Example (Static Factory) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Demonstrates a recommended pattern for asynchronous service initialization using a static factory method. This approach allows for asynchronous construction without blocking the constructor thread. ```C# public interface IRemoteConnectionFactory { Task ConnectAsync(); } public interface IRemoteConnection { Task PublishAsync(string channel, string message); Task DisposeAsync(); } public class Service : IService { private readonly IRemoteConnection _connection; private Service(IRemoteConnection connection) { _connection = connection; } public static async Task CreateAsync(IRemoteConnectionFactory connectionFactory) { return new Service(await connectionFactory.ConnectAsync()); } } ``` -------------------------------- ### ConcurrentDictionary GetOrAdd - Good Example (AsyncLazy Pattern) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Presents an improved implementation using the AsyncLazy pattern with ConcurrentDictionary.GetOrAdd. This prevents the value-constructing delegate from being executed multiple times concurrently, ensuring efficiency. ```C# public class PersonController : Controller { private AppDbContext _db; // This cache needs expiration private static ConcurrentDictionary> _cache = new ConcurrentDictionary>(); public PersonController(AppDbContext db) { _db = db; } public async Task Get(int id) { var person = await _cache.GetOrAdd(id, (key) => new AsyncLazy(() => _db.People.FindAsync(key))).Value; return Ok(person); } private class AsyncLazy : Lazy> { public AsyncLazy(Func> valueFactory) : base(valueFactory) { } } } ``` -------------------------------- ### ConcurrentDictionary GetOrAdd - Good Example (Storing Tasks) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Shows a correct implementation using ConcurrentDictionary.GetOrAdd by storing Tasks directly, thus avoiding thread-pool starvation. This approach allows asynchronous operations to proceed without blocking. ```C# public class PersonController : Controller { private AppDbContext _db; // This cache needs expiration private static ConcurrentDictionary> _cache = new ConcurrentDictionary>(); public PersonController(AppDbContext db) { _db = db; } public async Task Get(int id) { var person = await _cache.GetOrAdd(id, (key) => _db.People.FindAsync(key)); return Ok(person); } } ``` -------------------------------- ### Asynchronous Service Initialization - Bad Example (Constructor Blocking) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Illustrates a problematic approach to asynchronous service initialization where the constructor blocks on an asynchronous operation using Task.Result. This can lead to thread-pool starvation and deadlocks. ```C# public interface IRemoteConnectionFactory { Task ConnectAsync(); } public interface IRemoteConnection { Task PublishAsync(string channel, string message); Task DisposeAsync(); } public class Service : IService { private readonly IRemoteConnection _connection; public Service(IRemoteConnectionFactory connectionFactory) { _connection = connectionFactory.ConnectAsync().Result; } } ``` -------------------------------- ### Good: TaskCreationOptions.LongRunning for Queue Processing Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md This C# example demonstrates using Task.Factory.StartNew with TaskCreationOptions.LongRunning for processing a message queue. This approach leverages the Task Parallel Library (TPL) for better exception handling and integration with async patterns, while still ensuring a dedicated thread. ```C# public class QueueProcessor { private readonly BlockingCollection _messageQueue = new BlockingCollection(); public Task StartProcessing() => Task.Factory.StartNew(ProcessQueue, TaskCreationOptions.LongRunning); public void Enqueue(Message message) { _messageQueue.Add(message); } private void ProcessQueue() { foreach (var item in _messageQueue.GetConsumingEnumerable()) { ProcessItem(item); } } private void ProcessItem(Message message) { } } ``` -------------------------------- ### ConcurrentDictionary GetOrAdd - Bad Example (Thread-Pool Starvation) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Demonstrates an incorrect usage of ConcurrentDictionary.GetOrAdd where blocking on Task.Result can lead to thread-pool starvation. This pattern should be avoided. ```C# public class PersonController : Controller { private AppDbContext _db; // This cache needs expiration private static ConcurrentDictionary _cache = new ConcurrentDictionary(); public PersonController(AppDbContext db) { _db = db; } public IActionResult Get(int id) { var person = _cache.GetOrAdd(id, (key) => _db.People.FindAsync(key).Result); return Ok(person); } } ``` -------------------------------- ### GOOD: Copying HttpContext Data for Background Threads (C#) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AspNetCoreGuidance.md This C# code snippet shows the correct way to handle background tasks by explicitly copying necessary data, like the request path, from the HttpContext before the task starts. This avoids capturing the HttpContext itself, preventing potential issues. ```C# using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; public class GoodHttpContextScenario : Controller { [HttpGet("/fire-and-forget-3")] public IActionResult FireAndForget3() { string path = HttpContext.Request.Path; _ = Task.Run(async () => { await Task.Delay(1000); // This captures just the path Log(path); }); return Accepted(); } private void Log(string message) { /* Logging implementation */ } } ``` -------------------------------- ### Avoid adding headers after response started (BAD) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AspNetCoreGuidance.md This C# code snippet demonstrates an incorrect approach where response headers are added after the response body has already been written, which can lead to failure. ```C# app.Use(async (next, context) => { await context.Response.WriteAsync("Hello "); await next(); // This may fail if next() already wrote to the response context.Response.Headers["test"] = "value"; }); ``` -------------------------------- ### Flow CancellationToken to APIs Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Explains the necessity of flowing CancellationToken to asynchronous operations that support cancellation. This example shows how to pass the token to Stream.ReadAsync, enabling cooperative cancellation and ensuring that operations can be stopped gracefully. ```C# public async Task DoAsyncThing(CancellationToken cancellationToken = default) { byte[] buffer = new byte[1024]; // This properly flows cancellationToken to ReadAsync int read = await _stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken); return Encoding.UTF8.GetString(buffer, 0, read); } ``` -------------------------------- ### ExecutionContext Leak Example (BAD) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md This C# code demonstrates a memory leak caused by capturing the ExecutionContext, which implicitly holds onto AsyncLocal data. The `NumberCache` class, when using `CancellationToken.Register`, inadvertently stores `ChunkyObject` instances, preventing them from being garbage collected. ```C# using System.Collections.Concurrent; // Singleton cache var cache = new NumberCache(TimeSpan.FromHours(1)); var executionContext = ExecutionContext.Capture(); // Simulate 10000 concurrent requests Parallel.For(0, 10000, i => { // Restore the initial ExecutionContext per "request" ExecutionContext.Restore(executionContext!); ChunkyObject.Current = new ChunkyObject(); cache.Add(i); }); Console.WriteLine("Before GC: " + BytesAsString(GC.GetGCMemoryInfo().HeapSizeBytes)); Console.ReadLine(); GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("After GC: " + BytesAsString(GC.GetGCMemoryInfo().HeapSizeBytes)); Console.ReadLine(); static string BytesAsString(long bytes) { string[] suffix = { "B", "KB", "MB", "GB", "TB" }; int i; double doubleBytes = 0; for (i = 0; bytes / 1024 > 0; i++, bytes /= 1024) { doubleBytes = bytes / 1024.0; } return string.Format("{0:0.00} {1}", doubleBytes, suffix[i]); } public class NumberCache { private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); private TimeSpan _timeSpan; public NumberCache(TimeSpan timeSpan) { _timeSpan = timeSpan; } public void Add(int key) { var cts = _cache.GetOrAdd(key, _ => new CancellationTokenSource()); // Delete entry on expiration cts.Token.Register((_, _) => _cache.TryRemove(key, out _), null); // Start count down cts.CancelAfter(_timeSpan); } } class ChunkyObject { private static readonly AsyncLocal _current = new(); // Stores lots of data (but it should be gen0) private readonly string _data = new string('A', 1024 * 32); public static ChunkyObject? Current { get => _current.Value; set => _current.Value = value; } public string Data => _data; } ``` -------------------------------- ### Use OnStarting to set headers before response is sent (GOOD) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AspNetCoreGuidance.md This C# code snippet demonstrates the recommended approach using `HttpResponse.OnStarting`. It registers a callback to set headers just before the response headers are flushed, ensuring they are added correctly without needing to know the pipeline's state. ```C# app.Use(async (next, context) => { // Wire up the callback that will fire just before the response headers are sent to the client. context.Response.OnStarting(() => { context.Response.Headers["someheader"] = "somevalue"; return Task.CompletedTask; }); await next(); }); ``` -------------------------------- ### Add headers safely by checking HasStarted (GOOD) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AspNetCoreGuidance.md This C# code snippet shows a correct way to add response headers by first checking if the `HttpResponse.HasStarted` property is false before attempting to modify the headers. ```C# app.Use(async (next, context) => { await context.Response.WriteAsync("Hello "); await next(); // Check if the response has already started before adding header and writing if (!context.Response.HasStarted) { context.Response.Headers["test"] = "value"; } }); ``` -------------------------------- ### HTTP Client Request with Cancellation Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/Scenarios/wwwroot/index.html Demonstrates how to implement cancellation for HTTP client requests using `CancellationToken`. This is essential for responsive applications and preventing resource leaks. ```C# public async Task Cancellation1(CancellationToken cancellationToken) { var client = new HttpClient(); var response = await client.GetAsync("http://example.com/cancellation-1", cancellationToken); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(cancellationToken); return Ok(content); } ``` ```C# public async Task Cancellation2(CancellationToken cancellationToken) { var client = new HttpClient(); var response = await client.GetAsync("http://example.com/cancellation-2", cancellationToken); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(cancellationToken); return Ok(content); } ``` ```C# public async Task Cancellation3(CancellationToken cancellationToken) { var client = new HttpClient(); var response = await client.GetAsync("http://example.com/cancellation-3", cancellationToken); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(cancellationToken); return Ok(content); } ``` -------------------------------- ### Solution: Using StrongBox for Mutable AsyncLocal State Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Presents a solution using `StrongBox` to manage mutable state within an AsyncLocal. By storing the value in a mutable `StrongBox`, changes to the value can be propagated to all captured execution contexts. ```C# class DisposableThing : IDisposable { private static readonly AsyncLocal> _current = new(); private bool _disposed; public static DisposableThing? Current { get => _current.Value?.Value; set { var box = _current.Value; if (box is not null) { // Mutate the value in any execution context that was copied box.Value = null; } if (value is not null) { _current.Value = new StrongBox(value); } } } public int Value { get { if (_disposed) throw new ObjectDisposedException(GetType().FullName); return 1; } } public void Dispose() { _disposed = true; } } ``` -------------------------------- ### Async Void vs. Task-Returning Methods in Controllers Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Illustrates the dangers of 'async void' in controller actions, where unhandled exceptions can crash the process. Recommends using Task-returning methods for better exception handling via TaskScheduler. ```C# public class MyController : Controller { [HttpPost("/start")] public IActionResult Post() { BackgroundOperationAsync(); return Accepted(); } public async void BackgroundOperationAsync() { var result = await CallDependencyAsync(); DoSomething(result); } } ``` ```C# public class MyController : Controller { [HttpPost("/start")] public IActionResult Post() { Task.Run(BackgroundOperationAsync); return Accepted(); } public async Task BackgroundOperationAsync() { var result = await CallDependencyAsync(); DoSomething(result); } } ``` -------------------------------- ### Legacy WebClient vs. HttpClient for HTTP Requests Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/HttpClientGuidance.md Demonstrates the incorrect (synchronous) usage of the legacy WebClient for HTTP requests and the recommended (asynchronous) usage of HttpClient. HttpClient is the modern and preferred API for making HTTP requests in .NET. ```C# public string DoSomethingAsync() { var client = new WebClient(); return client.DownloadString("http://www.google.com"); } ``` ```C# static readonly HttpClient client = new HttpClient(); public async Task DoSomethingAsync() { return await client.GetStringAsync("http://www.google.com"); } ``` -------------------------------- ### Good: BackgroundQueue with Async Overload Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md This C# code snippet presents a good practice for a `BackgroundQueue` that offers both synchronous and asynchronous callback overloads, allowing for safer handling of background tasks. ```C# public class BackgroundQueue { public static void FireAndForget(Action action) { } public static void FireAndForget(Func action) { } } ``` -------------------------------- ### Correct Usage of RunImpersonated (Pre-.NET 5.0) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md This C# code snippet demonstrates the recommended way to use `WindowsIdentity.RunImpersonated` in frameworks earlier than .NET 5.0. It correctly awaits the result of the delegate passed to `RunImpersonated`, ensuring the operation runs within the impersonation context. ```C# public async Task> GetDataImpersonatedAsync(SafeAccessTokenHandle safeAccessTokenHandle) { return await WindowsIdentity.RunImpersonated( safeAccessTokenHandle, context => _db.QueryAsync("SELECT Name from Products")); } ``` -------------------------------- ### HTTP Client for JSON Response Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/Scenarios/wwwroot/index.html Illustrates using HttpClient to fetch a JSON response. This is a fundamental pattern for service-to-service communication in .NET Core applications. ```C# public async Task GetHttpClient1() { var client = new HttpClient(); var response = await client.GetAsync("http://example.com/httpclient-1"); response.EnsureSuccessStatusCode(); var jsonResponse = await response.Content.ReadAsStringAsync(); return Ok(jsonResponse); } ``` ```C# public async Task GetHttpClient2() { var client = new HttpClient(); var response = await client.GetAsync("http://example.com/httpclient-2"); response.EnsureSuccessStatusCode(); var jsonResponse = await response.Content.ReadAsStringAsync(); return Ok(jsonResponse); } ``` ```C# public async Task GetHttpClient3() { var client = new HttpClient(); var response = await client.GetAsync("http://example.com/httpclient-3"); response.EnsureSuccessStatusCode(); var jsonResponse = await response.Content.ReadAsStringAsync(); return Ok(jsonResponse); } ``` -------------------------------- ### Using Task.Run for Trivial Computations (Bad Practice) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Demonstrates the incorrect usage of Task.Run for a simple addition operation. This approach unnecessarily queues a work item to the thread pool, wasting resources as it immediately completes with the pre-computed value. ```C# public class MyLibrary { public Task AddAsync(int a, int b) { return Task.Run(() => a + b); } } ``` -------------------------------- ### Using ContinueWith vs. await (Bad vs. Good) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Compares the use of Task.ContinueWith for handling asynchronous operation results with the preferred async/await pattern. ContinueWith can have different semantics and does not capture the SynchronizationContext, unlike await. ```C# public Task DoSomethingAsync() { return CallDependencyAsync().ContinueWith(task => { return task.Result + 1; }); } ``` ```C# public async Task DoSomethingAsync() { var result = await CallDependencyAsync(); return result + 1; } ``` -------------------------------- ### Executing Async Operation in Business Logic Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/Scenarios/wwwroot/index.html Shows how to properly execute asynchronous operations within the business logic layer of an ASP.NET Core application, ensuring proper await and error handling. ```C# public async Task Async1() { return await Task.FromResult("Async operation 1 result"); } ``` ```C# public async Task Async2() { return await Task.FromResult("Async operation 2 result"); } ``` ```C# public async Task Async3() { return await Task.FromResult("Async operation 3 result"); } ``` ```C# public async Task Async4() { return await Task.FromResult("Async operation 4 result"); } ``` ```C# public async Task Async5() { return await Task.FromResult("Async operation 5 result"); } ``` ```C# public async Task Async6() { return await Task.FromResult("Async operation 6 result"); } ``` ```C# public async Task Async7() { return await Task.FromResult("Async operation 7 result"); } ``` ```C# public async Task Async8() { return await Task.FromResult("Async operation 8 result"); } ``` ```C# public async Task Async9() { return await Task.FromResult("Async operation 9 result"); } ``` ```C# public async Task Async10() { return await Task.FromResult("Async operation 10 result"); } ``` ```C# public async Task Async11() { return await Task.FromResult("Async operation 11 result"); } ``` -------------------------------- ### Correct Usage of RunImpersonatedAsync (.NET 5.0+) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md This C# code snippet shows the recommended usage of `WindowsIdentity.RunImpersonatedAsync`, available in .NET 5.0 and later. It correctly awaits the asynchronous operation, ensuring it executes within the impersonation context. ```C# public async Task> GetDataImpersonatedAsync(SafeAccessTokenHandle safeAccessTokenHandle) { return await WindowsIdentity.RunImpersonatedAsync( safeAccessTokenHandle, context => _db.QueryAsync("SELECT Name from Products")); } ``` -------------------------------- ### GOOD: Using IServiceScopeFactory for Background Threads (C#) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AspNetCoreGuidance.md This C# code snippet demonstrates the recommended practice for background tasks involving scoped services. It injects IServiceScopeFactory and creates a new scope within the background task to resolve services, ensuring they have the correct lifetime and preventing ObjectDisposedException. ```C# using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; public class PokemonDbContext : DbContext { public DbSet Pokemon { get; set; } public PokemonDbContext(DbContextOptions options) : base(options) { } } public class Pokemon { } public class GoodServiceScopeScenario : Controller { [HttpGet("/fire-and-forget-3")] public IActionResult FireAndForget3([FromServices] IServiceScopeFactory serviceScopeFactory) { // This version of fire and forget adds some exception handling. We're also no longer capturing the PokemonDbContext from the incoming request. // Instead, we're injecting an IServiceScopeFactory (which is a singleton) in order to create a scope in the background work item. _ = Task.Run(async () => { await Task.Delay(1000); // Create a scope for the lifetime of the background operation and resolve services from it using (var scope = serviceScopeFactory.CreateScope()) { // This will resolve a PokemonDbContext from the correct scope and the operation will succeed var context = scope.ServiceProvider.GetRequiredService(); context.Pokemon.Add(new Pokemon()); await context.SaveChangesAsync(); } }); return Accepted(); } } ``` -------------------------------- ### Prefer async/await over returning Task Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Illustrates the benefits of using `async`/`await` for exception normalization, code maintainability, and diagnostics compared to directly returning a `Task`. It shows the incorrect direct return and the preferred `async`/`await` pattern. ```C# public Task DoSomethingAsync() { return CallDependencyAsync(); } ``` ```C# public async Task DoSomethingAsync() { return await CallDependencyAsync(); } ``` -------------------------------- ### Good: Using PeriodicTimer (.NET 6+) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md This C# code snippet demonstrates the recommended approach using `PeriodicTimer` introduced in .NET 6. It handles asynchronous operations within a loop, improving reliability and avoiding common pitfalls. ```C# public class Pinger : IDisposable { private readonly PeriodicTimer _timer; private readonly HttpClient _client; public Pinger(HttpClient client) { _client = client; _timer = new PeriodicTimer(TimeSpan.FromSeconds(1)); _ = Task.Run(DoAsyncPings); } public void Dispose() { _timer.Dispose(); } private async Task DoAsyncPings() { while (await _timer.WaitForNextTickAsync()) { // TODO: Handle exceptions await _client.GetAsync("http://mybackend/api/ping"); } } } ``` -------------------------------- ### Asynchrony is Viral: Sync over Async vs. Async/Await Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Demonstrates the difference between blocking the thread with Task.Result (bad) and using await for non-blocking asynchronous operations (good). Highlights the 'async all the way' principle. ```C# public int DoSomethingAsync() { var result = CallDependencyAsync().Result; return result + 1; } ``` ```C# public async Task DoSomethingAsync() { var result = await CallDependencyAsync(); return result + 1; } ``` -------------------------------- ### Properly Dispose StreamWriter/Stream with FlushAsync Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Demonstrates how to correctly handle `StreamWriter` or `Stream` disposal to avoid blocking threads. It shows the incorrect blocking approach and the correct asynchronous approaches using `await using` or an explicit `FlushAsync` call before disposal. ```C# app.Run(async context => { // The implicit Dispose call will synchronously write to the response body using (var streamWriter = new StreamWriter(context.Response.Body)) { await streamWriter.WriteAsync("Hello World"); } }); ``` ```C# app.Run(async context => { // The implicit AsyncDispose call will flush asynchronously await using (var streamWriter = new StreamWriter(context.Response.Body)) { await streamWriter.WriteAsync("Hello World"); } }); ``` ```C# app.Run(async context => { using (var streamWriter = new StreamWriter(context.Response.Body)) { await streamWriter.WriteAsync("Hello World"); // Force an asynchronous flush await streamWriter.FlushAsync(); } }); ``` -------------------------------- ### TaskCompletionSource with RunContinuationsAsynchronously Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Demonstrates the correct way to create a TaskCompletionSource using TaskCreationOptions.RunContinuationsAsynchronously to prevent inline continuation execution, which can lead to deadlocks and thread-pool starvation. This ensures continuations are dispatched to the thread pool. ```C# public Task DoSomethingAsync() { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var operation = new LegacyAsyncOperation(); operation.Completed += result => { // Code awaiting on this task will resume on a different thread-pool thread tcs.SetResult(result); }; return tcs.Task; } ``` -------------------------------- ### Async Void in Controllers Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/Scenarios/wwwroot/index.html Explains the implications and best practices for using `async void` methods in ASP.NET Core controllers. While possible, it's generally discouraged due to error handling complexities. ```C# public async void AsyncVoid1() { await Task.Delay(100); // Potential issues with exception handling } ``` ```C# public async void AsyncVoid2() { await Task.Delay(100); // Potential issues with exception handling } ``` -------------------------------- ### Fire and Forget Database Operation Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/Scenarios/wwwroot/index.html Demonstrates the 'fire and forget' pattern for database operations, where the result of the operation is not immediately awaited. This is useful for background tasks or logging. ```C# public void FireAndForget1() { _ = Task.Run(() => DatabaseOperation1()); } ``` ```C# public void FireAndForget2() { _ = Task.Run(() => DatabaseOperation2()); } ``` ```C# public void FireAndForget3() { _ = Task.Run(() => DatabaseOperation3()); } ``` ```C# public void FireAndForget4() { _ = Task.Run(() => DatabaseOperation4()); } ``` ```C# public void FireAndForget5() { _ = Task.Run(() => DatabaseOperation5()); } ``` -------------------------------- ### Retrieving and Returning JSON Response Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/Scenarios/wwwroot/index.html Demonstrates how to retrieve a JSON response from an external API and return it within an ASP.NET Core application. This scenario is relevant for building APIs that consume data from other services. ```C# public async Task GetBigJsonContent1() { var client = new HttpClient(); var response = await client.GetAsync("http://example.com/big-json-content-1"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); return Content(content, "application/json"); } ``` ```C# public async Task GetBigJsonContent2() { var client = new HttpClient(); var response = await client.GetAsync("http://example.com/big-json-content-2"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); return Content(content, "application/json"); } ``` ```C# public async Task GetBigJsonContent3() { var client = new HttpClient(); var response = await client.GetAsync("http://example.com/big-json-content-3"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); return Content(content, "application/json"); } ``` ```C# public async Task GetBigJsonContent4() { var client = new HttpClient(); var response = await client.GetAsync("http://example.com/big-json-content-4"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); return Content(content, "application/json"); } ``` ```C# public async Task GetBigJsonContent5() { var client = new HttpClient(); var response = await client.GetAsync("http://example.com/big-json-content-5"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); return Content(content, "application/json"); } ``` -------------------------------- ### Using ValueTask for Trivial Computations (Optimized) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Presents an optimized approach using ValueTask for trivially computed values. This method avoids Task allocations on the managed heap and does not utilize extra threads, offering improved performance and memory efficiency. ```C# public class MyLibrary { public ValueTask AddAsync(int a, int b) { return new ValueTask(a + b); } } ``` -------------------------------- ### AsyncLocal Value Setting - GOOD Practice Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Demonstrates the recommended approach for setting AsyncLocal values within async methods. This ensures that execution context changes are properly scoped and restored, preventing unintended side effects. ```C# var local = new AsyncLocal(); await MethodA(); Console.WriteLine(local.Value); async Task MethodA() { local.Value = 1; await MethodB(); Console.WriteLine(local.Value); } async Task MethodB() { local.Value = 2; Console.WriteLine(local.Value); } ``` -------------------------------- ### Understanding ExecutionContext Hashing Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Illustrates how changing the value of an AsyncLocal creates a new execution context, demonstrated by differing hash codes. This highlights why simply setting the AsyncLocal to null doesn't affect already captured contexts. ```C# DisposableThing.Current = new DisposableThing(); Console.WriteLine("After setting thing " + ExecutionContext.Capture().GetHashCode()); DisposableThing.Current = null; Console.WriteLine("After setting Current to null " + ExecutionContext.Capture().GetHashCode()); ``` -------------------------------- ### Avoid Parallel HttpContext Access Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AspNetCoreGuidance.md Demonstrates how to safely access HttpContext from multiple threads in parallel by copying necessary data beforehand. Accessing HttpContext concurrently can lead to corruption and undefined behavior. ```C# public class AsyncController : Controller { [HttpGet("/search")] public async Task Get(string query) { var query1 = SearchAsync(SearchEngine.Google, query); var query2 = SearchAsync(SearchEngine.Bing, query); var query3 = SearchAsync(SearchEngine.DuckDuckGo, query); await Task.WhenAll(query1, query2, query3); var results1 = await query1; var results2 = await query2; var results3 = await query3; return SearchResults.Combine(results1, results2, results3); } private async Task SearchAsync(SearchEngine engine, string query) { var searchResults = SearchResults.Empty; try { _logger.LogInformation("Starting search query from {path}.", HttpContext.Request.Path); searchResults = await _searchService.SearchAsync(engine, query); _logger.LogInformation("Finishing search query from {path}.", HttpContext.Request.Path); } catch (Exception ex) { _logger.LogError(ex, "Failed query from {path}", HttpContext.Request.Path); } return searchResults; } } ``` ```C# public class AsyncController : Controller { [HttpGet("/search")] public async Task Get(string query) { string path = HttpContext.Request.Path; var query1 = SearchAsync(SearchEngine.Google, query, path); var query2 = SearchAsync(SearchEngine.Bing, query, path); var query3 = SearchAsync(SearchEngine.DuckDuckGo, query, path); await Task.WhenAll(query1, query2, query3); var results1 = await query1; var results2 = await query2; var results3 = await query3; return SearchResults.Combine(results1, results2, results3); } private async Task SearchAsync(SearchEngine engine, string query, string path) { var searchResults = SearchResults.Empty; try { _logger.LogInformation("Starting search query from {path}.", path); searchResults = await _searchService.SearchAsync(engine, query); _logger.LogInformation("Finishing search query from {path}.", path); } catch (Exception ex) { _logger.LogError(ex, "Failed query from {path}", path); } return searchResults; } } ``` -------------------------------- ### Using Task.FromResult for Trivial Computations (Good Practice) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Illustrates the recommended practice of using Task.FromResult for trivially computed values. This method efficiently wraps the computed data in a Task without engaging the thread pool, thus avoiding extra thread usage. ```C# public class MyLibrary { public Task AddAsync(int a, int b) { return Task.FromResult(a + b); } } ``` -------------------------------- ### Bad: Async Void Timer Callback Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md This C# code snippet demonstrates a bad practice where an `async void` method is used as a Timer callback. This can lead to process crashes if exceptions occur within the callback. ```C# public class Pinger { private readonly Timer _timer; private readonly HttpClient _client; public Pinger(HttpClient client) { _client = client; _timer = new Timer(Heartbeat, null, 1000, 1000); } public async void Heartbeat(object state) { await _client.GetAsync("http://mybackend/api/ping"); } } ``` -------------------------------- ### Good: Discarding Task in Timer Callback Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md This C# code snippet shows a good practice for Timer callbacks. It uses an `async Task`-based method and discards the returned Task, preventing process crashes on exceptions and firing `TaskScheduler.UnobservedTaskException` instead. ```C# public class Pinger { private readonly Timer _timer; private readonly HttpClient _client; public Pinger(HttpClient client) { _client = client; _timer = new Timer(Heartbeat, null, 1000, 1000); } public void Heartbeat(object state) { // Discard the result _ = DoAsyncPing(); } private async Task DoAsyncPing() { await _client.GetAsync("http://mybackend/api/ping"); } } ``` -------------------------------- ### Good: Dedicated Thread for Long-Running Queue Processing Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md This C# code snippet shows the recommended approach for long-running background tasks. It uses a dedicated thread (ThreadStart) to process a message queue, preventing the depletion of the thread pool. ```C# public class QueueProcessor { private readonly BlockingCollection _messageQueue = new BlockingCollection(); public void StartProcessing() { var thread = new Thread(ProcessQueue) { // This is important as it allows the process to exit while this thread is running IsBackground = true }; thread.Start(); } public void Enqueue(Message message) { _messageQueue.Add(message); } private void ProcessQueue() { foreach (var item in _messageQueue.GetConsumingEnumerable()) { ProcessItem(item); } } private void ProcessItem(Message message) { } } ``` -------------------------------- ### Incorrect Usage of RunImpersonated (Async Query Outside) Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md This C# code snippet demonstrates an incorrect way to use `WindowsIdentity.RunImpersonated` by executing an asynchronous query outside the impersonation context. This can lead to errors as the query might not run under the impersonated identity. ```C# public async Task> GetDataImpersonatedAsync(SafeAccessTokenHandle safeAccessTokenHandle) { Task> products = null; WindowsIdentity.RunImpersonated( safeAccessTokenHandle, context => { products = _db.QueryAsync("SELECT Name from Products"); }); return await products; } ``` -------------------------------- ### Potential Issue: Captured Reference Before Nulling Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Highlights a remaining risk even with the `StrongBox` solution: if a reference to the `AsyncLocal` value is captured before it's set to null, the disposed object might still be accessed. ```C# void Dispatch() { // Task.Run will capture the current execution context (which means async locals are captured in the callback) _ = Task.Run(async () => { // Get the current reference var current = DisposableThing.Current; // Delay for a second then log await Task.Delay(1000); ``` -------------------------------- ### Dispose CancellationTokenSource for Timeouts Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Illustrates the importance of disposing CancellationTokenSource objects used for timeouts to prevent them from lingering in the timer queue. Proper disposal ensures that timers are removed promptly after their intended use, reducing resource pressure. ```C# public async Task HttpClientAsyncWithCancellationGood() { using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) { using (var client = _httpClientFactory.CreateClient()) { var response = await client.GetAsync("http://backend/api/1", cts.Token); return await response.Content.ReadAsStreamAsync(); } } } ``` -------------------------------- ### Cancelling Async Operation without Native Support Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/Scenarios/wwwroot/index.html Provides strategies for cancelling asynchronous operations that do not natively support `CancellationToken`. This often involves custom logic or wrappers. ```C# public async Task LegacyCancellation1(CancellationToken cancellationToken) { // Simulate an operation without native cancellation support await Task.Delay(1000); cancellationToken.ThrowIfCancellationRequested(); // ... continue operation ... } ``` ```C# public async Task LegacyCancellation2(CancellationToken cancellationToken) { // Simulate an operation without native cancellation support await Task.Delay(1000); cancellationToken.ThrowIfCancellationRequested(); // ... continue operation ... } ``` ```C# public async Task LegacyCancellation3(CancellationToken cancellationToken) { // Simulate an operation without native cancellation support await Task.Delay(1000); cancellationToken.ThrowIfCancellationRequested(); // ... continue operation ... } ``` ```C# public async Task LegacyCancellation4(CancellationToken cancellationToken) { // Simulate an operation without native cancellation support await Task.Delay(1000); cancellationToken.ThrowIfCancellationRequested(); // ... continue operation ... } ``` -------------------------------- ### AsyncLocal Value Setting - BAD Practice Source: https://github.com/davidfowl/aspnetcorediagnosticscenarios/blob/master/AsyncGuidance.md Illustrates an incorrect way to set AsyncLocal values outside of async methods. This leads to execution context mutations propagating unexpectedly, causing confusing behavior and potential bugs. ```C# var local = new AsyncLocal(); MethodA(); Console.WriteLine(local.Value); void MethodA() { local.Value = 1; MethodB(); Console.WriteLine(local.Value); } void MethodB() { local.Value = 2; Console.WriteLine(local.Value); } ```