The async and concurrency cheat sheet
Every await decision, and the deadlock it avoids.
This pillar collects everything on the site about asynchronous and concurrent code - the async/await decisions in C#, cancellation, the .NET 11 runtime-async work, the locking and channel primitives, and the deadlocks and exceptions each wrong turn produces.
What to read first
Start with the two calls you make daily: 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 any of that still matters on modern hosts.
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. For running work in parallel, Parallel.ForEach vs Parallel.ForEachAsync vs Task.WhenAll decides, with the AggregateException WhenAll throws as the gotcha. For coordination, lock vs Monitor vs SemaphoreSlim vs System.Threading.Lock and Channels instead of BlockingCollection are the primitives. For shutdown, propagating a CancellationToken and CancelAfter timeouts are the pair.
What’s on this page
The list below auto-collects posts tagged with any of: async, concurrency, threading - which pulls in the Dart and Flutter async posts too. Newest first.
Companion pillars: C# 14 features and the .NET 11 tracker.
Index (38 posts)
2026 / 09
- How to find the async void handlers causing ANRs in a .NET MAUI Android app
Play Console tells you your ANR rate is over 0.47% and hands you a native stack full of libcoreclr.so frames. Here is how to get from that useless trace to the exact async void event handler that blocked the main thread, using a Looper printer, a SynchronizationContext wrapper that names the state machine, and dotnet-trace over dsrouter.
- How to use pessimistic locking with UPDLOCK and SELECT ... FOR UPDATE in EF Core 11
EF Core 11 still has no lock API. Here is how to take a real row lock with FromSql: WITH (UPDLOCK, ROWLOCK) on SQL Server, FOR UPDATE on PostgreSQL, the subquery trap that silently widens the lock, NOWAIT and SKIP LOCKED, deadlock retries, and what to do when the row does not exist yet.
- Returning a Task directly vs async/await passthrough in a C# repository method: which should you use?
Eliding async/await in a repository passthrough saves about 6 ns and 72 bytes, and costs you a stack frame, try/catch semantics, and safe disposal. Keep return await unless the method is a pure passthrough on a measured hot path.
2026 / 08
- Fix: 23505: duplicate key value violates unique constraint on a concurrent EF Core insert
The check-then-insert in your handler is not atomic. Catch PostgresException with SqlState 23505, or collapse the whole thing into one INSERT ... ON CONFLICT statement. EnableRetryOnFailure will not help.
- What is the difference between a Dart isolate and a thread?
A thread shares memory with every other thread in the process. A Dart isolate does not: it owns its heap, runs one event loop, and talks to other isolates only by messages. Here is what that means at the VM level, where isolate groups blur the line, and how it plays out in Flutter, FFI, and on the web.
- Fix: AggregateException "One or more errors occurred" when awaiting Task.WhenAll in C#
await Task.WhenAll rethrows only one of the failures. Keep the WhenAll task in a variable and read its Exception.InnerExceptions to see every error instead of one.
- Fix: CS1998 "This async method lacks 'await' operators and will run synchronously" in C#
CS1998 means an async method has no await, so it runs synchronously. Drop the async modifier and return Task.FromResult, or add the await you forgot.
- How to implement optimistic concurrency with a rowversion token in EF Core 11
Add a rowversion concurrency token in EF Core 11: the [Timestamp] and IsRowVersion setup, the SQL EF actually emits, catching DbUpdateConcurrencyException, store-wins vs client-wins vs merge, disconnected APIs with ETags, and the five traps that silently disable the whole thing.
2026 / 07
- How to test time-dependent code with TimeProvider and FakeTimeProvider in .NET 11
Replace DateTime.UtcNow, Stopwatch, and Task.Delay with System.TimeProvider so tests can control the clock: DI registration, FakeTimeProvider.Advance and SetUtcNow, testing timeouts and PeriodicTimer-based BackgroundServices, plus the Advance-continuation and xUnit v2 gotchas.
- Migrate from blocking .Result/.Wait() calls to async all the way up in a legacy C# codebase
A staged playbook for removing sync-over-async from an existing .NET codebase: inventory with analyzers, measure ThreadPool starvation, convert one call chain at a time, and ratchet the count to zero on .NET 11.
- .Result vs .Wait() vs GetAwaiter().GetResult() vs await in C#: which should you use?
await is the right answer almost every time. When you truly must block, GetAwaiter().GetResult() beats .Result and .Wait() because it throws the original exception. A decision matrix for .NET 11 and C# 14.
- Fix: CS4014 "Because this call is not awaited, execution of the current method continues" in C#
CS4014 means you called a Task-returning method without awaiting it. Add await, or discard with _ = if fire-and-forget is truly intended, and handle exceptions.
- Fix: deadlock when calling .Result or .Wait() on an async method in C#
Blocking on an async Task with .Result or .Wait() deadlocks when a SynchronizationContext is present. Here is why it hangs and how to fix it in .NET 11 and C# 14.
- How to disable Riverpod 3.0's automatic provider retry
Riverpod 3.0 retries a failed provider up to 10 times by default. Pass a retry function that returns null on ProviderScope, ProviderContainer, or an individual provider to turn it off or bound it.
- How to cancel a StreamSubscription in dispose to avoid a setState-after-dispose crash in Flutter
A stream keeps emitting after the user leaves the screen, its onData calls setState on a disposed State, and Flutter throws. Store the subscription, cancel it in dispose before super.dispose, and the callback can never fire on a dead widget. The full pattern for Flutter 3.44.
- How to guard setState with the mounted check after an async gap in Flutter
After an await, the widget may already be disposed, and calling setState throws. Guard the resume with if (!mounted) return; and, better, cancel the work that triggers it. The full pattern for Flutter 3.44.
- .NET 11 Runtime Async Drops the EnablePreviewFeatures Flag
As the .NET 11 previews progress toward the November release, Runtime Async has graduated: net11.0 projects opt in with a single MSBuild property, and the runtime libraries themselves now ship compiled on it.
- How to implement and consume IAsyncDisposable with await using in C#
A complete guide to IAsyncDisposable in C#: when to use await using, how to write DisposeAsync and DisposeAsyncCore correctly, and the stacking and ConfigureAwait gotchas that leak resources.
- How to propagate a CancellationToken through async methods in .NET 11
Thread a CancellationToken cleanly through every layer of an async call chain in .NET 11: last-parameter convention, default values, linked tokens, ASP.NET Core RequestAborted, and the CA2016 analyzer that catches the ones you drop.
- How to time out an async operation with CancellationTokenSource.CancelAfter in C#
Use CancellationTokenSource.CancelAfter to enforce an async deadline in .NET 11: constructor vs CancelAfter, linked tokens for composing timeouts with caller tokens, exception disambiguation, Task.WaitAsync, TryReset for pooling, and testable timeouts with TimeProvider.
2026 / 06
- How to use BuildContext safely after an await in Flutter
Capture what you need from the context before the await, then guard the resume with if (context.mounted) return. Here is the full pattern, the lint that enforces it, and the edge cases it misses.
- 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
- How to run fire-and-forget work safely in ASP.NET Core with BackgroundService
Calling Task.Run from a controller loses work on shutdown, swallows exceptions, and captures disposed scoped services. The safe pattern is a bounded Channel queue drained by a BackgroundService that opens a fresh scope per work item and drains in-flight work on StopAsync.
- Parallel.ForEach vs Parallel.ForEachAsync vs Task.WhenAll in C#
Use Parallel.ForEach for CPU-bound work over in-memory data, Parallel.ForEachAsync for async I/O over many items with a concurrency cap, and Task.WhenAll for a small fixed fan-out where you want every operation in flight and need the results back.
- lock vs Monitor vs SemaphoreSlim vs System.Threading.Lock in C#
Four ways to guard a critical section in C#, and a decision matrix for picking one. Use System.Threading.Lock for synchronous mutual exclusion on .NET 9+, SemaphoreSlim when the section spans an await, and Monitor only when you need Wait/Pulse.
- Task.Run vs Task.Factory.StartNew vs ThreadPool.QueueUserWorkItem
Three ways to push work onto the thread pool in C#, and which one to reach for. Use Task.Run for almost everything, ThreadPool.QueueUserWorkItem<TState> for allocation-free fire-and-forget, and Task.Factory.StartNew only for LongRunning or a custom scheduler.
- ConfigureAwait(false) vs default in .NET 11: does it still matter?
ConfigureAwait(false) is still mandatory in library code that may run under a SynchronizationContext (WinForms, WPF, MAUI). In application code on ASP.NET Core, a console app, or a worker service running on .NET 11, it is a no-op.
- IEnumerable vs IAsyncEnumerable vs IQueryable in C#: which one should the method return?
Three sequence interfaces, three execution models. Use IQueryable when a database can translate the query, IAsyncEnumerable when the producer is async and you want to stream, IEnumerable for everything else in memory.
- async void vs async Task in C#: when each is correct
async Task is the default and async void is the exception. Use async void only for event handlers, top-level message-loop handlers, and a handful of framework callbacks that demand a void signature. Everywhere else, async Task wins on exceptions, composition, and testability.
- Fix: A second operation was started on this context instance before a previous operation completed
EF Core throws when two awaits run in parallel on the same DbContext. Await each call sequentially, or get a new DbContext per concurrent unit of work via IDbContextFactory.
- How to write a Dart isolate for CPU-bound work
When async/await is not enough: spawn a Dart isolate to run CPU-bound work off the UI thread. Isolate.run, Flutter's compute, long-lived workers with SendPort/ReceivePort, what can cross the boundary, and the JS/web caveat. Tested on Dart 3.11 and Flutter 3.27.1.
2026 / 04
- How to use the new System.Threading.Lock type in .NET 11
System.Threading.Lock arrived in .NET 9 and is the default synchronization primitive on .NET 11 and C# 14. This guide shows how to migrate from lock(object), how EnterScope works, and the gotchas around await, dynamic, and downlevel targets.
- How to use Channels instead of BlockingCollection in C#
System.Threading.Channels is the async-first replacement for BlockingCollection in .NET 11. This guide shows how to migrate, how to choose bounded vs unbounded, and how to handle backpressure, cancellation, and graceful shutdown without deadlocking.
- How to cancel a long-running Task in C# without deadlocking
Cooperative cancellation with CancellationToken, CancelAsync, Task.WaitAsync, and linked tokens in .NET 11. Plus the blocking patterns that turn a clean cancel into a deadlock.
- .NET 11 Runtime Async Replaces State Machines with Cleaner Stack Traces
Runtime Async in .NET 11 moves async/await handling from compiler-generated state machines into the runtime itself, producing readable stack traces, correct breakpoints, and fewer heap allocations.