Start Debugging

Async and concurrency in C#: a practical guide

Practical await, cancellation, and coordination decisions.

This site uses Walter, an AI agent, for research, writing, and maintenance. Editorial and verification process

This guide collects C# and .NET articles about asynchronous and concurrent code: async/await, cancellation, runtime-async work, locks, channels, and common deadlocks and exceptions.

What to read first

Start with the daily pair: async void vs async Task and .Result vs .Wait() vs GetAwaiter().GetResult() vs await, then the deadlock blocking causes and migrating a legacy codebase to async all the way up. ConfigureAwait(false) vs default in .NET 11 answers whether that still matters.

For return types, what ValueTask is and when it’s worth it, IEnumerable vs IAsyncEnumerable vs IQueryable, and returning a Task directly vs an async passthrough cover the signature choices; stuck in sync code, wait on a ValueTask without allocating. For running work in parallel, Parallel.ForEach vs Parallel.ForEachAsync vs Task.WhenAll decides, with inspect all failures from Task.WhenAll as the gotcha. For coordination, lock vs Monitor vs SemaphoreSlim vs System.Threading.Lock and Channels instead of BlockingCollection are the primitives. Below the locks, Volatile.Read vs Volatile.ReadBarrier picks the fence, Thread.Sleep vs Task.Delay vs SpinWait the way to wait, AsyncLocal<T> flow the context rule, and Lazy<T> vs LazyInitializer vs double-checked locking the way to initialize once. For shutdown, propagating a CancellationToken and CancelAfter timeouts are the pair.

For external programs, waiting for a process in C# demonstrates WaitForExit, WaitForExitAsync, exit codes, and timeout handling in a runnable project. Canceling the wait and terminating the child are separate operations.

What’s on this page

The index collects C# and .NET articles about async, concurrency, or threading, including EF Core coordination patterns. It is ordered newest first. For Dart isolates and Flutter asynchronous UI, use the Flutter guide.

Companion pillars: C# 14 features and the .NET 11 tracker.

Index (46 posts)

2026 / 11

  • How to initialize interdependent async singletons at startup in ASP.NET Core

    Give each singleton a memoized InitializeAsync that awaits its own dependencies, then await them all from IHostedLifecycleService.StartingAsync. Registration order stops mattering, Kestrel does not accept requests until init finishes, and a failure stops the host. Compared against init before app.Run, IHostedService.StartAsync, and a BackgroundService with a readiness check on ASP.NET Core 10.0.10.

  • What is AsyncLocal<T>, and why doesn't a value set inside an awaited method flow back to the caller?

    AsyncLocal<T> stores a value in the ExecutionContext, which flows down into awaits, Task.Run and continuations but never back up: every async method restores the caller's context when it returns. Verified on .NET 10: eleven cases with real output, the runtime code that does the restore, and three patterns that do get a value back to the caller.

  • What happens if you start a thread from a finalizer in .NET?

    Starting a thread inside a finalizer works, but the new thread is a normal-priority foreground thread that resurrects the object, outlives GC.WaitForPendingFinalizers, can crash the process, and may touch objects that were already finalized. Blocking on it hangs shutdown. Eight cases reproduced on .NET 10 with real output, plus the safe handoff pattern.

  • What Does EF Core Write Back Into an Entity After SaveChangesAsync?

    After SaveChangesAsync, EF Core 11 copies store-generated keys, defaults, computed columns and concurrency tokens back into your entities, fixes up foreign keys and navigations, and resets every entry to Unchanged. Here is exactly what gets written, when, what the SQL looks like, and four cases where the value on your entity is not what is in the database.

2026 / 10

  • MudBlazor 9.12: Slow ServerData and Autocomplete Searches No Longer Overwrite Newer Results

    MudBlazor 9.12.0 fixes a long-standing race in MudAutocomplete, MudDataGrid and MudTable: the result of the search that finished last used to win, not the one started last. Here is how the fix works and what changes for your ServerData and SearchFunc code.

  • Lazy<T> vs LazyInitializer.EnsureInitialized vs double-checked locking in C#

    Use Lazy<T> by default: it runs the factory once, caches exceptions, and its reads are too cheap to measure after initialization. Use LazyInitializer.EnsureInitialized when you lazily fill many fields or array slots and cannot afford an extra object per value. Hand-written double-checked locking is almost never worth it on .NET 10.

  • Thread.Sleep vs Task.Delay vs SpinWait for waiting in C#

    Use await Task.Delay for any timed wait in async code, Thread.Sleep only on a dedicated thread you own, and SpinWait only for sub-microsecond waits inside lock-free code. To wait for a condition, use a signal (ManualResetEventSlim, SemaphoreSlim, TaskCompletionSource) instead of any of the three. Measured on .NET 10.0.10.

  • How to tail a log file in C# while another process is still writing to it

    Open the log with FileShare.ReadWrite | FileShare.Delete, seek to the end, poll for new bytes, decode them with your own UTF-8 Decoder instead of StreamReader, and handle truncation and rotation explicitly. A tested tail -F implementation for .NET 10 and .NET 11.

  • How to wait for a ValueTask synchronously in a non-async method without allocating

    Check IsCompleted first and read the result through GetAwaiter().GetResult(), which costs 0 bytes. Only fall back to blocking when the ValueTask is still pending, and do that with a cached event and UnsafeOnCompleted instead of AsTask(), which allocates 72 to 208 bytes per call. Measured on .NET 10 and .NET 11 RC1.

  • Volatile.Read vs Volatile.ReadBarrier in .NET 10

    Volatile.Read is an acquire load of one location. Volatile.ReadBarrier, new in .NET 10, is a fence that gives every earlier read acquire semantics. Use Volatile.Read for flags and published references, and ReadBarrier when you need a batch of plain or non-atomic reads to complete before the next memory access, as in a seqlock.

2026 / 09

2026 / 08

2026 / 07

2026 / 06

  • What is ValueTask<T> and when is it worth it?

    ValueTask and ValueTask<T> are structs that let an async method return a result without heap-allocating a Task when it completes synchronously. The win is one fewer allocation on hot paths that usually finish without awaiting. The cost is a strict await-once contract. Here is what the type actually is, how it works, and the narrow set of cases where it earns its keep.

  • What is IAsyncEnumerable<T> and when should I use it?

    IAsyncEnumerable<T> is the interface for asynchronous streams: a sequence whose elements arrive over time and each one may require an await. Here is what it actually is, how await foreach and yield drive it, and the rule for when to reach for it over Task<List<T>>.

  • Migrate from ValueTask<T> back to Task<T>: when and why (.NET 11, C# 14)

    A practical checklist for reverting ValueTask and ValueTask<T> return types to Task and Task<T>, what breaks at the call sites, how to verify each change, and how to know whether the swap was ever worth it.

  • Fix: ObjectDisposedException: Cannot access a disposed context instance

    Your fire-and-forget task captured a request-scoped DbContext that the DI scope already disposed. Resolve a fresh context inside the task with IServiceScopeFactory or IDbContextFactory.

2026 / 05

2026 / 04

All pillars Home