What do
asyncandawaitdo in C#?asynclets a method useawait;awaitpauses the method until aTaskcompletes without blocking the thread, and the rest runs later as a continuation.C#using System; using System.Threading.Tasks; class Program { static async Task<int> DownloadSizeAsync(string url) { await Task.Delay(100); // stands in for a network call return url.Length * 100; } static async Task RunAsync() { int size = await DownloadSizeAsync("coddy.tech"); Console.WriteLine("size: " + size); } static void Main() { RunAsync().Wait(); // C# 7.1 and later allow async Task Main instead } }No thread is busy while the method waits on I/O, which is why async lets a server handle more requests. See async and await.
In what order does the code around an
awaitrun?C#using System; using System.Threading.Tasks; class Program { static async Task Work() { Console.Write("A"); await Task.Delay(50); Console.Write("C"); } static void Main() { Task t = Work(); Console.Write("B"); t.Wait(); Console.WriteLine(); } }Work()runs synchronously until its firstawaiton an unfinished task, soAprints inside the call. It then returns a pendingTask;MainprintsBand blocks ont.Wait(), and the continuation printsC. Code before the firstawaitruns on the caller's thread.What is the difference between a
Taskand aThread?A
Threadis an OS thread you manage; aTaskis an operation and its result, which may use no thread at all.Task.Runwork runs on a thread pool thread, and I/O tasks hold no thread while they wait. Tasks also return values, rethrow exceptions onawait, and compose withWhenAll.What is the difference between
Task.WhenAll,Task.WaitAllandTask.WhenAny?WhenAllreturns a task that completes when every task completes;WaitAllblocks the thread until they finish;WhenAnycompletes with the first task to finish.C#using System; using System.Diagnostics; using System.Threading.Tasks; class Program { static async Task<int> Fetch(int id, int ms) { await Task.Delay(ms); return id * 10; } static async Task RunAsync() { var sw = Stopwatch.StartNew(); int[] results = await Task.WhenAll(Fetch(1, 300), Fetch(2, 300), Fetch(3, 300)); Console.WriteLine(string.Join(",", results)); Console.WriteLine(sw.ElapsedMilliseconds < 600 ? "ran concurrently" : "ran one by one"); Task<int> first = await Task.WhenAny(Fetch(4, 400), Fetch(5, 50)); Console.WriteLine("first result: " + first.Result); } static void Main() { RunAsync().Wait(); } }Three 300 ms calls take about 300 ms. Trap: awaiting
WhenAllrethrows only the first exception, while theWhenAlltask'sException.InnerExceptionsholds all of them.Why should you avoid
async void?An
async voidmethod cannot be awaited, so the caller cannot tell when it finishes or catch its exceptions, which usually crash the process.async void SaveBad() { await db.SaveAsync(); } // fire and forget, exceptions escape async Task SaveGood() { await db.SaveAsync(); } // caller can await and catch try { SaveBad(); } catch { /* never runs for async exceptions */ }Use it only for event handlers, with a
tryandcatchinside. Anasynclambda passed as anActionis alsoasync void.What is
ValueTask, and when should you use it instead ofTask?ValueTask<T>is a struct that can carry a result directly, so a method that usually completes synchronously (a cache hit) avoids allocating aTask. Use it on such hot paths andTaskelsewhere.private readonly Dictionary<int, string> cache = new(); public ValueTask<string> GetNameAsync(int id) { if (cache.TryGetValue(id, out var name)) return new ValueTask<string>(name); // no allocation return new ValueTask<string>(LoadAndCacheAsync(id)); // wraps a real Task }Await a
ValueTaskexactly once, and never read.Resultbefore it completes.Why can
.Resultor.Wait()on an async method deadlock, and what doesConfigureAwait(false)change?With a single-threaded synchronization context (WinForms, WPF, classic ASP.NET),
.Resultblocks the thread the awaited method needs to resume on, so each waits for the other.// WPF button handler var data = LoadAsync().Result; // blocks the UI thread async Task<string> LoadAsync() { var s = await http.GetStringAsync(url); // wants to resume on the UI thread return s; // never gets there }Await all the way. In library code,
ConfigureAwait(false)resumes on the thread pool instead of the captured context. ASP.NET Core has no synchronization context, so it does not deadlock this way.How do you cancel an async operation in C#?
Pass a
CancellationTokendown the call chain; the caller cancels through itsCancellationTokenSource, and the code passes the token on or callsThrowIfCancellationRequested().C#using System; using System.Threading; using System.Threading.Tasks; class Program { static async Task<int> SlowWork(CancellationToken token) { int steps = 0; for (int i = 0; i < 100; i++) { token.ThrowIfCancellationRequested(); await Task.Delay(20, token); steps++; } return steps; } static async Task RunAsync() { var cts = new CancellationTokenSource(); cts.CancelAfter(100); try { await SlowWork(cts.Token); Console.WriteLine("finished"); } catch (OperationCanceledException) { Console.WriteLine("canceled"); } } static void Main() { RunAsync().Wait(); } }Cancellation is cooperative: code that never checks the token cannot be stopped.
What does the
lockstatement do, and when do you useInterlockedinstead?lock (obj)lets one thread at a time run the block for that object, throughMonitor.EnterandMonitor.Exitin atryandfinally(on a C# 13System.Threading.Lock, throughEnterScope(); see the lock statement docs). For a single counter,Interlocked.Incrementis a single atomic operation.C#using System; using System.Threading; using System.Threading.Tasks; class Program { static int unsafeCount, lockedCount, atomicCount; static readonly object gate = new object(); static void Main() { Parallel.For(0, 100000, i => { unsafeCount++; // race: read, add, write lock (gate) { lockedCount++; } Interlocked.Increment(ref atomicCount); }); Console.WriteLine("locked: " + lockedCount); Console.WriteLine("atomic: " + atomicCount); Console.WriteLine("unsafe is exact: " + (unsafeCount == 100000)); } }The unsynchronized counter usually loses updates, so its line can print
FalseorTrue. You cannotawaitinside alock; useSemaphoreSlim.WaitAsync. See lock.How does async/await work under the hood?
The compiler rewrites an
asyncmethod into a state machine: locals that live across anawaitbecome fields, and eachawaitbecomes a numbered state. At anawait, if the task is already complete the method carries on synchronously; otherwise it registersMoveNextas the continuation and returns a pendingTaskto the caller.When the task finishes,
MoveNextresumes at the saved state, on the capturedSynchronizationContextif there is one, otherwise usually on a thread pool thread. No thread waits during theawait, and any type with a suitableGetAwaiter()method can be awaited.
C# Async and Await Interview Questions
Tasks, threads, async and await, cancellation, deadlocks and thread safety.
10 questions