What is the difference between async and multithreading in C#?
Async/await is about freeing threads during I/O waits (non-blocking), whereas multithreading is about running CPU-bound work across multiple cores. Learn more in Task vs Thread vs ThreadPool and async/await in ASP.NET Core.
Why does an ASP.NET Core API hang with idle CPU?
This is usually thread pool starvation caused by calling .Result or .Wait() on async methods (sync-over-async), which blocks workers. See how to diagnose and fix it in ThreadPool Starvation and Task.Run vs await.
When is async void vs async Task acceptable in C#?
Always return Task or Task<T> on Web APIs so the host can observe completion and handle exceptions. async void is only for UI event handlers. Read the rules in async/await in ASP.NET Core.
What is an async deadlock in C#?
Classic deadlocks happen when blocking on a Task with a custom SynchronizationContext (like WPF or legacy ASP.NET). ASP.NET Core has no request sync context, so it starves the pool instead of deadlocking. Read ConfigureAwait(false) and ThreadPool Starvation.
Should I use CancellationToken, Channels, or rate limiting?
async/await lets an ASP.NET Core API wait on SQL or HTTP without holding a thread. Task vs async void, CancellationToken, .Result starvation, WhenAll, and HttpClient.
Thread pool starvation is queued work with no free workers. C# .Result and .Wait on ASP.NET Core cause idle-CPU 504s — diagnose with dotnet-counters, then make the call chain async.
Task.Run queues CPU work on the ThreadPool; await yields the worker during I/O. On ASP.NET Core the request is already on the pool — wrapping ToListAsync makes 504s worse.
A Task is a promise that work will finish; a Thread is an OS worker. C# async/await is not multithreading — Task vs Thread vs ThreadPool, Sleep vs Delay, and when ValueTask is worth it.
A CancellationToken is a cooperative please-stop flag. In ASP.NET Core, bind RequestAborted, pass it to EF Core and HttpClient, link timeouts, and do not log client abort as a 500.
If you only write ASP.NET Core APIs, skip ConfigureAwait(false). It is a library contract for WPF/MAUI SynchronizationContext — not a Core performance trick, and not a .Result amnesty.
A BackgroundService is a hosted worker the generic host starts and stops. Use stoppingToken, not RequestAborted. Create a DI scope per message. Task.Run after Ok() is not a worker.
C# async await interview questions with scenario answers — .Result starvation, async void, Task.WhenAll with EF Core, CancellationToken, and ValueTask.
TaskCompletionSource creates a Task you complete yourself. Use it to wrap event-based APIs (EAP) into TAP: TrySetResult when Connected fires, then callers await instead of WaitOne.
Skip this if you only write ASP.NET Core APIs. await Task.Yield() lets the WPF/MAUI message pump run, then continues on the UI thread. Prefer Task.Run for CPU. Yield is not a Core performance trick.
Task.WhenAll waits for many I/O tasks without blocking a thread. WaitAll blocks the pool. Cap 10,000 HTTP calls, never WhenAll two queries on one DbContext, Parallel.ForEachAsync is for CPU or throttled I/O.
A semaphore is a handful of tickets. SemaphoreSlim.WaitAsync is the async lock for outbound HttpClient — bulkhead (max in flight), why lock cannot await, versus inbound rate limiting.
lock protects a short in-memory critical section so two threads cannot corrupt shared data. lock is Monitor.Enter/Exit. You cannot await inside. Mutex is cross-process; async code uses SemaphoreSlim.
Interlocked updates one variable in an uninterruptible CPU step so two threads cannot both read 5 and both write 6. Use Increment for counters; CompareExchange (CAS) for flags; a lock when two fields must change together.
IAsyncEnumerable lets you process SQL rows one at a time instead of loading 100,000 entities into RAM. yield return, AsAsyncEnumerable, EnumeratorCancellation, and streaming HTTP.
ConcurrentDictionary is a thread-safe map, not a cache policy. GetOrAdd’s factory can run twice, keys must include tenant id, and a lock is still right when you mutate a value in place.
Producer-consumer means one part of the app enqueues work and another processes it. Channel<T> waits without parking a thread; BlockingCollection.Take does. Bounded backpressure vs a durable bus.
ThreadLocal sticks to an OS thread; after await you are often on another ThreadPool worker. AsyncLocal flows with ExecutionContext. Prefer a tenantId parameter; ambient current-user is how clinic B saw clinic A.