[{"data":1,"prerenderedAt":1177},["ShallowReactive",2],{"blog-\u002Fblog\u002Fdotnet-async-await-state-machine-explained":3},{"id":4,"title":5,"body":6,"description":1162,"difficulty":1163,"extension":1164,"framework":1165,"frameworkSlug":1166,"meta":1167,"navigation":145,"order":57,"path":1168,"qaPath":1169,"seo":1170,"stem":1171,"subtopic":1172,"topic":1173,"topicSlug":1174,"updated":1175,"__hash__":1176},"blog\u002Fblog\u002Fdotnet-async-await-state-machine-explained.md","How C# Async\u002FAwait Works Under the Hood",{"type":7,"value":8,"toc":1150},"minimark",[9,14,26,30,44,91,94,266,280,284,360,367,371,385,455,460,467,515,525,529,607,614,662,666,744,755,782,786,796,866,874,918,930,934,1032,1036,1093,1109,1113,1146],[10,11,13],"h2",{"id":12},"why-asyncawait-mastery-separates-good-from-great-c-developers","Why async\u002Fawait mastery separates good from great C# developers",[15,16,17,21,22,25],"p",{},[18,19,20],"code",{},"async","\u002F",[18,23,24],{},"await"," is C#'s most impactful feature for I\u002FO-bound applications — yet most\ndevelopers only understand the happy path. Interviewers specifically probe the edges:\ndeadlocks, ConfigureAwait, ValueTask, cancellation, and exception propagation. This\narticle walks through how it actually works and what to watch for.",[10,27,29],{"id":28},"what-the-compiler-actually-generates","What the compiler actually generates",[15,31,32,34,35,39,40,43],{},[18,33,20],{}," is 100% syntactic sugar. The compiler transforms an async method into a ",[36,37,38],"strong",{},"state\nmachine"," — a struct implementing ",[18,41,42],{},"IAsyncStateMachine",". Consider:",[45,46,51],"pre",{"className":47,"code":48,"language":49,"meta":50,"style":50},"language-csharp shiki shiki-themes github-light github-dark","public async Task\u003Cstring> FetchAsync(string url)\n{\n    var response = await httpClient.GetAsync(url);\n    var body     = await response.Content.ReadAsStringAsync();\n    return body;\n}\n","csharp","",[18,52,53,61,67,73,79,85],{"__ignoreMap":50},[54,55,58],"span",{"class":56,"line":57},"line",1,[54,59,60],{},"public async Task\u003Cstring> FetchAsync(string url)\n",[54,62,64],{"class":56,"line":63},2,[54,65,66],{},"{\n",[54,68,70],{"class":56,"line":69},3,[54,71,72],{},"    var response = await httpClient.GetAsync(url);\n",[54,74,76],{"class":56,"line":75},4,[54,77,78],{},"    var body     = await response.Content.ReadAsStringAsync();\n",[54,80,82],{"class":56,"line":81},5,[54,83,84],{},"    return body;\n",[54,86,88],{"class":56,"line":87},6,[54,89,90],{},"}\n",[15,92,93],{},"The compiler generates something equivalent to:",[45,95,97],{"className":47,"code":96,"language":49,"meta":50,"style":50},"\u002F\u002F Pseudocode — the actual generated code is more complex:\nprivate struct FetchAsyncStateMachine : IAsyncStateMachine\n{\n    public int _state;     \u002F\u002F which await point we're at\n    public string _url;\n    private HttpResponseMessage _response;\n    private TaskAwaiter\u003CHttpResponseMessage> _awaiter1;\n    private TaskAwaiter\u003Cstring> _awaiter2;\n\n    public void MoveNext()\n    {\n        switch (_state)\n        {\n            case 0:\n                _awaiter1 = httpClient.GetAsync(_url).GetAwaiter();\n                if (!_awaiter1.IsCompleted) { _state = 1; \u002F* schedule continuation *\u002F return; }\n                goto case 1;\n            case 1:\n                _response = _awaiter1.GetResult();\n                _awaiter2 = _response.Content.ReadAsStringAsync().GetAwaiter();\n                if (!_awaiter2.IsCompleted) { _state = 2; return; }\n                goto case 2;\n            case 2:\n                var body = _awaiter2.GetResult();\n                \u002F\u002F complete the outer Task\u003Cstring> with 'body'\n                break;\n        }\n    }\n}\n",[18,98,99,104,109,113,118,123,128,134,140,147,153,159,165,171,177,183,189,195,201,207,213,219,225,231,237,243,249,255,261],{"__ignoreMap":50},[54,100,101],{"class":56,"line":57},[54,102,103],{},"\u002F\u002F Pseudocode — the actual generated code is more complex:\n",[54,105,106],{"class":56,"line":63},[54,107,108],{},"private struct FetchAsyncStateMachine : IAsyncStateMachine\n",[54,110,111],{"class":56,"line":69},[54,112,66],{},[54,114,115],{"class":56,"line":75},[54,116,117],{},"    public int _state;     \u002F\u002F which await point we're at\n",[54,119,120],{"class":56,"line":81},[54,121,122],{},"    public string _url;\n",[54,124,125],{"class":56,"line":87},[54,126,127],{},"    private HttpResponseMessage _response;\n",[54,129,131],{"class":56,"line":130},7,[54,132,133],{},"    private TaskAwaiter\u003CHttpResponseMessage> _awaiter1;\n",[54,135,137],{"class":56,"line":136},8,[54,138,139],{},"    private TaskAwaiter\u003Cstring> _awaiter2;\n",[54,141,143],{"class":56,"line":142},9,[54,144,146],{"emptyLinePlaceholder":145},true,"\n",[54,148,150],{"class":56,"line":149},10,[54,151,152],{},"    public void MoveNext()\n",[54,154,156],{"class":56,"line":155},11,[54,157,158],{},"    {\n",[54,160,162],{"class":56,"line":161},12,[54,163,164],{},"        switch (_state)\n",[54,166,168],{"class":56,"line":167},13,[54,169,170],{},"        {\n",[54,172,174],{"class":56,"line":173},14,[54,175,176],{},"            case 0:\n",[54,178,180],{"class":56,"line":179},15,[54,181,182],{},"                _awaiter1 = httpClient.GetAsync(_url).GetAwaiter();\n",[54,184,186],{"class":56,"line":185},16,[54,187,188],{},"                if (!_awaiter1.IsCompleted) { _state = 1; \u002F* schedule continuation *\u002F return; }\n",[54,190,192],{"class":56,"line":191},17,[54,193,194],{},"                goto case 1;\n",[54,196,198],{"class":56,"line":197},18,[54,199,200],{},"            case 1:\n",[54,202,204],{"class":56,"line":203},19,[54,205,206],{},"                _response = _awaiter1.GetResult();\n",[54,208,210],{"class":56,"line":209},20,[54,211,212],{},"                _awaiter2 = _response.Content.ReadAsStringAsync().GetAwaiter();\n",[54,214,216],{"class":56,"line":215},21,[54,217,218],{},"                if (!_awaiter2.IsCompleted) { _state = 2; return; }\n",[54,220,222],{"class":56,"line":221},22,[54,223,224],{},"                goto case 2;\n",[54,226,228],{"class":56,"line":227},23,[54,229,230],{},"            case 2:\n",[54,232,234],{"class":56,"line":233},24,[54,235,236],{},"                var body = _awaiter2.GetResult();\n",[54,238,240],{"class":56,"line":239},25,[54,241,242],{},"                \u002F\u002F complete the outer Task\u003Cstring> with 'body'\n",[54,244,246],{"class":56,"line":245},26,[54,247,248],{},"                break;\n",[54,250,252],{"class":56,"line":251},27,[54,253,254],{},"        }\n",[54,256,258],{"class":56,"line":257},28,[54,259,260],{},"    }\n",[54,262,264],{"class":56,"line":263},29,[54,265,90],{},[15,267,268,269,272,273,276,277,279],{},"Key insight: ",[36,270,271],{},"no thread is blocked",". When the Task is not complete, ",[18,274,275],{},"MoveNext"," returns\nand the calling thread is released. A completion callback is registered on the I\u002FO\noperation; when it fires, ",[18,278,275],{}," is called again from wherever the runtime decides\n(ThreadPool, SynchronizationContext).",[10,281,283],{"id":282},"task-vs-thread-a-critical-distinction","Task vs Thread — a critical distinction",[45,285,287],{"className":47,"code":286,"language":49,"meta":50,"style":50},"\u002F\u002F Thread: OS resource, ~1 MB stack, kernel overhead\nvar thread = new Thread(() => Console.WriteLine(\"thread\"));\nthread.Start(); \u002F\u002F allocates a thread for the duration\n\n\u002F\u002F Task: a promise of a result — may or may not need a thread\nvar task = Task.Run(() => Console.WriteLine(\"pool thread\"));\n\u002F\u002F Task.Run uses the ThreadPool\n\n\u002F\u002F I\u002FO-bound async Task: ZERO threads while waiting\nasync Task\u003Cstring> ReadFileAsync(string path)\n{\n    \u002F\u002F Thread released during file I\u002FO:\n    return await File.ReadAllTextAsync(path);\n    \u002F\u002F Thread resumes (possibly a different pool thread) when I\u002FO completes\n}\n",[18,288,289,294,299,304,308,313,318,323,327,332,337,341,346,351,356],{"__ignoreMap":50},[54,290,291],{"class":56,"line":57},[54,292,293],{},"\u002F\u002F Thread: OS resource, ~1 MB stack, kernel overhead\n",[54,295,296],{"class":56,"line":63},[54,297,298],{},"var thread = new Thread(() => Console.WriteLine(\"thread\"));\n",[54,300,301],{"class":56,"line":69},[54,302,303],{},"thread.Start(); \u002F\u002F allocates a thread for the duration\n",[54,305,306],{"class":56,"line":75},[54,307,146],{"emptyLinePlaceholder":145},[54,309,310],{"class":56,"line":81},[54,311,312],{},"\u002F\u002F Task: a promise of a result — may or may not need a thread\n",[54,314,315],{"class":56,"line":87},[54,316,317],{},"var task = Task.Run(() => Console.WriteLine(\"pool thread\"));\n",[54,319,320],{"class":56,"line":130},[54,321,322],{},"\u002F\u002F Task.Run uses the ThreadPool\n",[54,324,325],{"class":56,"line":136},[54,326,146],{"emptyLinePlaceholder":145},[54,328,329],{"class":56,"line":142},[54,330,331],{},"\u002F\u002F I\u002FO-bound async Task: ZERO threads while waiting\n",[54,333,334],{"class":56,"line":149},[54,335,336],{},"async Task\u003Cstring> ReadFileAsync(string path)\n",[54,338,339],{"class":56,"line":155},[54,340,66],{},[54,342,343],{"class":56,"line":161},[54,344,345],{},"    \u002F\u002F Thread released during file I\u002FO:\n",[54,347,348],{"class":56,"line":167},[54,349,350],{},"    return await File.ReadAllTextAsync(path);\n",[54,352,353],{"class":56,"line":173},[54,354,355],{},"    \u002F\u002F Thread resumes (possibly a different pool thread) when I\u002FO completes\n",[54,357,358],{"class":56,"line":179},[54,359,90],{},[15,361,362,363,366],{},"A server handling 10,000 concurrent I\u002FO-bound requests with ",[18,364,365],{},"async\u002Fawait"," may use only\na handful of threads — each request releases its thread while waiting for the network\nor disk. With synchronous blocking, each request needs a dedicated thread: 10,000 threads\n= ~10 GB of stack memory.",[10,368,370],{"id":369},"synchronizationcontext-and-configureawaitfalse","SynchronizationContext and ConfigureAwait(false)",[15,372,373,374,376,377,380,381,384],{},"After an ",[18,375,24],{},", C# tries to resume on the ",[36,378,379],{},"SynchronizationContext"," that was current\nwhen the method suspended. In WPF\u002FWinForms, this is the UI thread dispatcher. In\nASP.NET Classic, it was the request context. In ASP.NET Core and console apps, there is\nno SynchronizationContext (",[18,382,383],{},"null"," means \"run on any pool thread\").",[45,386,388],{"className":47,"code":387,"language":49,"meta":50,"style":50},"\u002F\u002F WPF — continuation resumes on UI thread (default behavior):\nprivate async void Button_Click(object sender, EventArgs e)\n{\n    var data = await GetDataAsync();\n    label.Content = data; \u002F\u002F safe: we're on the UI thread\n}\n\n\u002F\u002F Library code — doesn't need to return to any specific context:\npublic async Task\u003Cstring> GetDataAsync()\n{\n    \u002F\u002F ConfigureAwait(false): resume on any pool thread, no context marshalling\n    var raw = await httpClient.GetStringAsync(url).ConfigureAwait(false);\n    return Process(raw);\n}\n",[18,389,390,395,400,404,409,414,418,422,427,432,436,441,446,451],{"__ignoreMap":50},[54,391,392],{"class":56,"line":57},[54,393,394],{},"\u002F\u002F WPF — continuation resumes on UI thread (default behavior):\n",[54,396,397],{"class":56,"line":63},[54,398,399],{},"private async void Button_Click(object sender, EventArgs e)\n",[54,401,402],{"class":56,"line":69},[54,403,66],{},[54,405,406],{"class":56,"line":75},[54,407,408],{},"    var data = await GetDataAsync();\n",[54,410,411],{"class":56,"line":81},[54,412,413],{},"    label.Content = data; \u002F\u002F safe: we're on the UI thread\n",[54,415,416],{"class":56,"line":87},[54,417,90],{},[54,419,420],{"class":56,"line":130},[54,421,146],{"emptyLinePlaceholder":145},[54,423,424],{"class":56,"line":136},[54,425,426],{},"\u002F\u002F Library code — doesn't need to return to any specific context:\n",[54,428,429],{"class":56,"line":142},[54,430,431],{},"public async Task\u003Cstring> GetDataAsync()\n",[54,433,434],{"class":56,"line":149},[54,435,66],{},[54,437,438],{"class":56,"line":155},[54,439,440],{},"    \u002F\u002F ConfigureAwait(false): resume on any pool thread, no context marshalling\n",[54,442,443],{"class":56,"line":161},[54,444,445],{},"    var raw = await httpClient.GetStringAsync(url).ConfigureAwait(false);\n",[54,447,448],{"class":56,"line":167},[54,449,450],{},"    return Process(raw);\n",[54,452,453],{"class":56,"line":173},[54,454,90],{},[15,456,457],{},[36,458,459],{},"The deadlock trap:",[15,461,462,463,466],{},"A single-threaded SynchronizationContext + blocking ",[18,464,465],{},".Result"," call = deadlock:",[45,468,470],{"className":47,"code":469,"language":49,"meta":50,"style":50},"\u002F\u002F Deadlock in WPF\u002FWinForms or ASP.NET Classic:\nstring data = GetDataAsync().Result; \u002F\u002F blocks UI\u002Frequest thread\n\u002F\u002F GetDataAsync's continuation tries to resume on the same thread — BLOCKED\n\u002F\u002F Result: neither side can proceed — deadlock!\n\n\u002F\u002F Fix 1: go async all the way\nstring data = await GetDataAsync();\n\n\u002F\u002F Fix 2: break context capture in library (ConfigureAwait(false) in GetDataAsync)\n",[18,471,472,477,482,487,492,496,501,506,510],{"__ignoreMap":50},[54,473,474],{"class":56,"line":57},[54,475,476],{},"\u002F\u002F Deadlock in WPF\u002FWinForms or ASP.NET Classic:\n",[54,478,479],{"class":56,"line":63},[54,480,481],{},"string data = GetDataAsync().Result; \u002F\u002F blocks UI\u002Frequest thread\n",[54,483,484],{"class":56,"line":69},[54,485,486],{},"\u002F\u002F GetDataAsync's continuation tries to resume on the same thread — BLOCKED\n",[54,488,489],{"class":56,"line":75},[54,490,491],{},"\u002F\u002F Result: neither side can proceed — deadlock!\n",[54,493,494],{"class":56,"line":81},[54,495,146],{"emptyLinePlaceholder":145},[54,497,498],{"class":56,"line":87},[54,499,500],{},"\u002F\u002F Fix 1: go async all the way\n",[54,502,503],{"class":56,"line":130},[54,504,505],{},"string data = await GetDataAsync();\n",[54,507,508],{"class":56,"line":136},[54,509,146],{"emptyLinePlaceholder":145},[54,511,512],{"class":56,"line":142},[54,513,514],{},"\u002F\u002F Fix 2: break context capture in library (ConfigureAwait(false) in GetDataAsync)\n",[15,516,517,520,521,524],{},[36,518,519],{},"Rule:"," In library code, always use ",[18,522,523],{},"ConfigureAwait(false)",". In application code\nthat updates UI or accesses request-scoped services, don't.",[10,526,528],{"id":527},"taskwhenall-and-taskwhenany","Task.WhenAll and Task.WhenAny",[45,530,532],{"className":47,"code":531,"language":49,"meta":50,"style":50},"\u002F\u002F Parallel I\u002FO — 3 independent fetches in parallel:\nvar t1 = FetchUserAsync(1);   \u002F\u002F starts immediately\nvar t2 = FetchUserAsync(2);   \u002F\u002F starts immediately\nvar t3 = FetchUserAsync(3);   \u002F\u002F starts immediately\n\u002F\u002F All three are in flight simultaneously\n\nUser[] users = await Task.WhenAll(t1, t2, t3);\n\u002F\u002F Total time ≈ max(t1, t2, t3) — not t1 + t2 + t3\n\n\u002F\u002F WhenAny — timeout pattern:\nvar work    = DoLongWorkAsync();\nvar timeout = Task.Delay(TimeSpan.FromSeconds(30));\nif (await Task.WhenAny(work, timeout) == timeout)\n    throw new TimeoutException(\"Operation exceeded 30 s\");\nvar result = await work;\n",[18,533,534,539,544,549,554,559,563,568,573,577,582,587,592,597,602],{"__ignoreMap":50},[54,535,536],{"class":56,"line":57},[54,537,538],{},"\u002F\u002F Parallel I\u002FO — 3 independent fetches in parallel:\n",[54,540,541],{"class":56,"line":63},[54,542,543],{},"var t1 = FetchUserAsync(1);   \u002F\u002F starts immediately\n",[54,545,546],{"class":56,"line":69},[54,547,548],{},"var t2 = FetchUserAsync(2);   \u002F\u002F starts immediately\n",[54,550,551],{"class":56,"line":75},[54,552,553],{},"var t3 = FetchUserAsync(3);   \u002F\u002F starts immediately\n",[54,555,556],{"class":56,"line":81},[54,557,558],{},"\u002F\u002F All three are in flight simultaneously\n",[54,560,561],{"class":56,"line":87},[54,562,146],{"emptyLinePlaceholder":145},[54,564,565],{"class":56,"line":130},[54,566,567],{},"User[] users = await Task.WhenAll(t1, t2, t3);\n",[54,569,570],{"class":56,"line":136},[54,571,572],{},"\u002F\u002F Total time ≈ max(t1, t2, t3) — not t1 + t2 + t3\n",[54,574,575],{"class":56,"line":142},[54,576,146],{"emptyLinePlaceholder":145},[54,578,579],{"class":56,"line":149},[54,580,581],{},"\u002F\u002F WhenAny — timeout pattern:\n",[54,583,584],{"class":56,"line":155},[54,585,586],{},"var work    = DoLongWorkAsync();\n",[54,588,589],{"class":56,"line":161},[54,590,591],{},"var timeout = Task.Delay(TimeSpan.FromSeconds(30));\n",[54,593,594],{"class":56,"line":167},[54,595,596],{},"if (await Task.WhenAny(work, timeout) == timeout)\n",[54,598,599],{"class":56,"line":173},[54,600,601],{},"    throw new TimeoutException(\"Operation exceeded 30 s\");\n",[54,603,604],{"class":56,"line":179},[54,605,606],{},"var result = await work;\n",[15,608,609,610,613],{},"Exception handling with ",[18,611,612],{},"WhenAll",":",[45,615,617],{"className":47,"code":616,"language":49,"meta":50,"style":50},"var tasks = new[] { t1, t2, t3 };\ntry { await Task.WhenAll(tasks); }\ncatch\n{\n    \u002F\u002F 'await' re-throws only the first exception\n    \u002F\u002F Inspect all faults via the Task objects:\n    foreach (var t in tasks.Where(t => t.IsFaulted))\n        logger.LogError(t.Exception!.InnerException, \"Task failed\");\n}\n",[18,618,619,624,629,634,638,643,648,653,658],{"__ignoreMap":50},[54,620,621],{"class":56,"line":57},[54,622,623],{},"var tasks = new[] { t1, t2, t3 };\n",[54,625,626],{"class":56,"line":63},[54,627,628],{},"try { await Task.WhenAll(tasks); }\n",[54,630,631],{"class":56,"line":69},[54,632,633],{},"catch\n",[54,635,636],{"class":56,"line":75},[54,637,66],{},[54,639,640],{"class":56,"line":81},[54,641,642],{},"    \u002F\u002F 'await' re-throws only the first exception\n",[54,644,645],{"class":56,"line":87},[54,646,647],{},"    \u002F\u002F Inspect all faults via the Task objects:\n",[54,649,650],{"class":56,"line":130},[54,651,652],{},"    foreach (var t in tasks.Where(t => t.IsFaulted))\n",[54,654,655],{"class":56,"line":136},[54,656,657],{},"        logger.LogError(t.Exception!.InnerException, \"Task failed\");\n",[54,659,660],{"class":56,"line":142},[54,661,90],{},[10,663,665],{"id":664},"async-void-the-exception-swallower","async void — the exception swallower",[45,667,669],{"className":47,"code":668,"language":49,"meta":50,"style":50},"\u002F\u002F async void — exception escapes to SynchronizationContext (process crash):\nasync void LoadBad()\n{\n    await Task.Delay(100);\n    throw new Exception(\"boom\"); \u002F\u002F crashes the app!\n}\n\n\u002F\u002F async Task — exception stored in the Task:\nasync Task LoadGood()\n{\n    await Task.Delay(100);\n    throw new Exception(\"boom\"); \u002F\u002F caught when awaited\n}\n\ntry { await LoadGood(); }\ncatch (Exception ex) { \u002F* caught correctly *\u002F }\n",[18,670,671,676,681,685,690,695,699,703,708,713,717,721,726,730,734,739],{"__ignoreMap":50},[54,672,673],{"class":56,"line":57},[54,674,675],{},"\u002F\u002F async void — exception escapes to SynchronizationContext (process crash):\n",[54,677,678],{"class":56,"line":63},[54,679,680],{},"async void LoadBad()\n",[54,682,683],{"class":56,"line":69},[54,684,66],{},[54,686,687],{"class":56,"line":75},[54,688,689],{},"    await Task.Delay(100);\n",[54,691,692],{"class":56,"line":81},[54,693,694],{},"    throw new Exception(\"boom\"); \u002F\u002F crashes the app!\n",[54,696,697],{"class":56,"line":87},[54,698,90],{},[54,700,701],{"class":56,"line":130},[54,702,146],{"emptyLinePlaceholder":145},[54,704,705],{"class":56,"line":136},[54,706,707],{},"\u002F\u002F async Task — exception stored in the Task:\n",[54,709,710],{"class":56,"line":142},[54,711,712],{},"async Task LoadGood()\n",[54,714,715],{"class":56,"line":149},[54,716,66],{},[54,718,719],{"class":56,"line":155},[54,720,689],{},[54,722,723],{"class":56,"line":161},[54,724,725],{},"    throw new Exception(\"boom\"); \u002F\u002F caught when awaited\n",[54,727,728],{"class":56,"line":167},[54,729,90],{},[54,731,732],{"class":56,"line":173},[54,733,146],{"emptyLinePlaceholder":145},[54,735,736],{"class":56,"line":179},[54,737,738],{},"try { await LoadGood(); }\n",[54,740,741],{"class":56,"line":185},[54,742,743],{},"catch (Exception ex) { \u002F* caught correctly *\u002F }\n",[15,745,746,747,750,751,754],{},"The ",[36,748,749],{},"only"," acceptable ",[18,752,753],{},"async void"," is event handlers:",[45,756,758],{"className":47,"code":757,"language":49,"meta":50,"style":50},"private async void Button_Click(object sender, EventArgs e)\n{\n    try { await DoWorkAsync(); }\n    catch (Exception ex) { ShowError(ex); } \u002F\u002F must handle internally\n}\n",[18,759,760,764,768,773,778],{"__ignoreMap":50},[54,761,762],{"class":56,"line":57},[54,763,399],{},[54,765,766],{"class":56,"line":63},[54,767,66],{},[54,769,770],{"class":56,"line":69},[54,771,772],{},"    try { await DoWorkAsync(); }\n",[54,774,775],{"class":56,"line":75},[54,776,777],{},"    catch (Exception ex) { ShowError(ex); } \u002F\u002F must handle internally\n",[54,779,780],{"class":56,"line":81},[54,781,90],{},[10,783,785],{"id":784},"valuetask-when-to-skip-the-heap-allocation","ValueTask — when to skip the heap allocation",[15,787,788,791,792,795],{},[18,789,790],{},"Task\u003CT>"," is a class — every call allocates a heap object. ",[18,793,794],{},"ValueTask\u003CT>"," is a struct:",[45,797,799],{"className":47,"code":798,"language":49,"meta":50,"style":50},"\u002F\u002F Cache hit path: synchronously available — Task\u003CT> still allocates!\npublic async Task\u003Cint> GetTaskAsync()\n{\n    if (_cache.TryGetValue(\"n\", out int v)) return v; \u002F\u002F still wraps in a Task!\n    return await FetchAsync();\n}\n\n\u002F\u002F ValueTask: zero allocation on the fast path:\npublic ValueTask\u003Cint> GetValueTaskAsync()\n{\n    if (_cache.TryGetValue(\"n\", out int v))\n        return new ValueTask\u003Cint>(v); \u002F\u002F struct, no heap allocation\n    return new ValueTask\u003Cint>(FetchAsync());\n}\n",[18,800,801,806,811,815,820,825,829,833,838,843,847,852,857,862],{"__ignoreMap":50},[54,802,803],{"class":56,"line":57},[54,804,805],{},"\u002F\u002F Cache hit path: synchronously available — Task\u003CT> still allocates!\n",[54,807,808],{"class":56,"line":63},[54,809,810],{},"public async Task\u003Cint> GetTaskAsync()\n",[54,812,813],{"class":56,"line":69},[54,814,66],{},[54,816,817],{"class":56,"line":75},[54,818,819],{},"    if (_cache.TryGetValue(\"n\", out int v)) return v; \u002F\u002F still wraps in a Task!\n",[54,821,822],{"class":56,"line":81},[54,823,824],{},"    return await FetchAsync();\n",[54,826,827],{"class":56,"line":87},[54,828,90],{},[54,830,831],{"class":56,"line":130},[54,832,146],{"emptyLinePlaceholder":145},[54,834,835],{"class":56,"line":136},[54,836,837],{},"\u002F\u002F ValueTask: zero allocation on the fast path:\n",[54,839,840],{"class":56,"line":142},[54,841,842],{},"public ValueTask\u003Cint> GetValueTaskAsync()\n",[54,844,845],{"class":56,"line":149},[54,846,66],{},[54,848,849],{"class":56,"line":155},[54,850,851],{},"    if (_cache.TryGetValue(\"n\", out int v))\n",[54,853,854],{"class":56,"line":161},[54,855,856],{},"        return new ValueTask\u003Cint>(v); \u002F\u002F struct, no heap allocation\n",[54,858,859],{"class":56,"line":167},[54,860,861],{},"    return new ValueTask\u003Cint>(FetchAsync());\n",[54,863,864],{"class":56,"line":173},[54,865,90],{},[15,867,868,870,871,613],{},[18,869,794],{}," constraints — ",[36,872,873],{},"await it exactly once",[45,875,877],{"className":47,"code":876,"language":49,"meta":50,"style":50},"var vt = GetValueTaskAsync();\nvar a = await vt; \u002F\u002F ok\nvar b = await vt; \u002F\u002F undefined behaviour! don't do this\n\n\u002F\u002F If you need to await multiple times, convert to Task\u003CT>:\nTask\u003Cint> task = GetValueTaskAsync().AsTask();\nvar x = await task;\nvar y = await task; \u002F\u002F fine — Task can be awaited multiple times\n",[18,878,879,884,889,894,898,903,908,913],{"__ignoreMap":50},[54,880,881],{"class":56,"line":57},[54,882,883],{},"var vt = GetValueTaskAsync();\n",[54,885,886],{"class":56,"line":63},[54,887,888],{},"var a = await vt; \u002F\u002F ok\n",[54,890,891],{"class":56,"line":69},[54,892,893],{},"var b = await vt; \u002F\u002F undefined behaviour! don't do this\n",[54,895,896],{"class":56,"line":75},[54,897,146],{"emptyLinePlaceholder":145},[54,899,900],{"class":56,"line":81},[54,901,902],{},"\u002F\u002F If you need to await multiple times, convert to Task\u003CT>:\n",[54,904,905],{"class":56,"line":87},[54,906,907],{},"Task\u003Cint> task = GetValueTaskAsync().AsTask();\n",[54,909,910],{"class":56,"line":130},[54,911,912],{},"var x = await task;\n",[54,914,915],{"class":56,"line":136},[54,916,917],{},"var y = await task; \u002F\u002F fine — Task can be awaited multiple times\n",[15,919,920,926,927,929],{},[36,921,922,923,925],{},"Default to ",[18,924,790],{},"."," Use ",[18,928,794],{}," only in hot paths (high-frequency calls\nwhere the fast synchronous path is common) and after profiling confirms the allocation\noverhead matters.",[10,931,933],{"id":932},"cancellationtoken-the-cooperative-cancellation-model","CancellationToken — the cooperative cancellation model",[45,935,937],{"className":47,"code":936,"language":49,"meta":50,"style":50},"\u002F\u002F Caller: create source, set timeout, pass token\nusing var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));\n\ntry { await DoWorkAsync(cts.Token); }\ncatch (OperationCanceledException) { Console.WriteLine(\"Cancelled or timed out\"); }\n\n\u002F\u002F Method: accept and forward the token\npublic async Task DoWorkAsync(CancellationToken ct = default)\n{\n    \u002F\u002F Forward to awaitable calls:\n    await httpClient.GetAsync(url, ct);           \u002F\u002F cancels the HTTP request\n    await Task.Delay(5000, ct);                   \u002F\u002F cancels the delay\n\n    \u002F\u002F Manual check in CPU-bound loops:\n    for (int i = 0; i \u003C 1_000_000; i++)\n    {\n        ct.ThrowIfCancellationRequested();\n        Process(i);\n    }\n}\n",[18,938,939,944,949,953,958,963,967,972,977,981,986,991,996,1000,1005,1010,1014,1019,1024,1028],{"__ignoreMap":50},[54,940,941],{"class":56,"line":57},[54,942,943],{},"\u002F\u002F Caller: create source, set timeout, pass token\n",[54,945,946],{"class":56,"line":63},[54,947,948],{},"using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));\n",[54,950,951],{"class":56,"line":69},[54,952,146],{"emptyLinePlaceholder":145},[54,954,955],{"class":56,"line":75},[54,956,957],{},"try { await DoWorkAsync(cts.Token); }\n",[54,959,960],{"class":56,"line":81},[54,961,962],{},"catch (OperationCanceledException) { Console.WriteLine(\"Cancelled or timed out\"); }\n",[54,964,965],{"class":56,"line":87},[54,966,146],{"emptyLinePlaceholder":145},[54,968,969],{"class":56,"line":130},[54,970,971],{},"\u002F\u002F Method: accept and forward the token\n",[54,973,974],{"class":56,"line":136},[54,975,976],{},"public async Task DoWorkAsync(CancellationToken ct = default)\n",[54,978,979],{"class":56,"line":142},[54,980,66],{},[54,982,983],{"class":56,"line":149},[54,984,985],{},"    \u002F\u002F Forward to awaitable calls:\n",[54,987,988],{"class":56,"line":155},[54,989,990],{},"    await httpClient.GetAsync(url, ct);           \u002F\u002F cancels the HTTP request\n",[54,992,993],{"class":56,"line":161},[54,994,995],{},"    await Task.Delay(5000, ct);                   \u002F\u002F cancels the delay\n",[54,997,998],{"class":56,"line":167},[54,999,146],{"emptyLinePlaceholder":145},[54,1001,1002],{"class":56,"line":173},[54,1003,1004],{},"    \u002F\u002F Manual check in CPU-bound loops:\n",[54,1006,1007],{"class":56,"line":179},[54,1008,1009],{},"    for (int i = 0; i \u003C 1_000_000; i++)\n",[54,1011,1012],{"class":56,"line":185},[54,1013,158],{},[54,1015,1016],{"class":56,"line":191},[54,1017,1018],{},"        ct.ThrowIfCancellationRequested();\n",[54,1020,1021],{"class":56,"line":197},[54,1022,1023],{},"        Process(i);\n",[54,1025,1026],{"class":56,"line":203},[54,1027,260],{},[54,1029,1030],{"class":56,"line":209},[54,1031,90],{},[10,1033,1035],{"id":1034},"iasyncenumerable-streaming-without-buffering-everything","IAsyncEnumerable — streaming without buffering everything",[45,1037,1039],{"className":47,"code":1038,"language":49,"meta":50,"style":50},"\u002F\u002F Producer: yields items asynchronously one at a time\npublic async IAsyncEnumerable\u003COrder> StreamOrdersAsync(\n    [EnumeratorCancellation] CancellationToken ct = default)\n{\n    await foreach (var order in dbContext.Orders.AsAsyncEnumerable().WithCancellation(ct))\n        yield return order;\n}\n\n\u002F\u002F Consumer: processes items as they arrive — never buffers all orders\nawait foreach (var order in StreamOrdersAsync(cancellationToken))\n    await ProcessOrderAsync(order);\n",[18,1040,1041,1046,1051,1056,1060,1065,1070,1074,1078,1083,1088],{"__ignoreMap":50},[54,1042,1043],{"class":56,"line":57},[54,1044,1045],{},"\u002F\u002F Producer: yields items asynchronously one at a time\n",[54,1047,1048],{"class":56,"line":63},[54,1049,1050],{},"public async IAsyncEnumerable\u003COrder> StreamOrdersAsync(\n",[54,1052,1053],{"class":56,"line":69},[54,1054,1055],{},"    [EnumeratorCancellation] CancellationToken ct = default)\n",[54,1057,1058],{"class":56,"line":75},[54,1059,66],{},[54,1061,1062],{"class":56,"line":81},[54,1063,1064],{},"    await foreach (var order in dbContext.Orders.AsAsyncEnumerable().WithCancellation(ct))\n",[54,1066,1067],{"class":56,"line":87},[54,1068,1069],{},"        yield return order;\n",[54,1071,1072],{"class":56,"line":130},[54,1073,90],{},[54,1075,1076],{"class":56,"line":136},[54,1077,146],{"emptyLinePlaceholder":145},[54,1079,1080],{"class":56,"line":142},[54,1081,1082],{},"\u002F\u002F Consumer: processes items as they arrive — never buffers all orders\n",[54,1084,1085],{"class":56,"line":149},[54,1086,1087],{},"await foreach (var order in StreamOrdersAsync(cancellationToken))\n",[54,1089,1090],{"class":56,"line":155},[54,1091,1092],{},"    await ProcessOrderAsync(order);\n",[15,1094,1095,1096,1099,1100,1104,1105,1108],{},"Compare to ",[18,1097,1098],{},"Task\u003CIEnumerable\u003COrder>>",": that waits until ",[1101,1102,1103],"em",{},"all"," rows are loaded before the\ncaller gets the first one. ",[18,1106,1107],{},"IAsyncEnumerable\u003CT>"," pipelines the work — rows are processed\nas they come off the database cursor.",[10,1110,1112],{"id":1111},"recap","Recap",[15,1114,1115,21,1117,1119,1120,1122,1123,21,1125,1128,1129,1132,1133,1135,1136,1138,1139,1142,1143,1145],{},[18,1116,20],{},[18,1118,24],{}," transforms methods into state machines — no threads are blocked during\nI\u002FO. ",[18,1121,523],{}," in library code prevents context-capture overhead and\ndeadlocks. Never block async code with ",[18,1124,465],{},[18,1126,1127],{},".Wait()"," on a single-threaded\nSynchronizationContext. Run independent I\u002FO tasks concurrently with ",[18,1130,1131],{},"Task.WhenAll",".\nNever use ",[18,1134,753],{}," outside event handlers — exceptions disappear silently. Use\n",[18,1137,794],{}," only in allocation-sensitive hot paths. Pass ",[18,1140,1141],{},"CancellationToken"," through\nevery layer and forward it to every awaitable call. Use ",[18,1144,1107],{}," for large\nor unbounded data streams.",[1147,1148,1149],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":50,"searchDepth":63,"depth":63,"links":1151},[1152,1153,1154,1155,1156,1157,1158,1159,1160,1161],{"id":12,"depth":63,"text":13},{"id":28,"depth":63,"text":29},{"id":282,"depth":63,"text":283},{"id":369,"depth":63,"text":370},{"id":527,"depth":63,"text":528},{"id":664,"depth":63,"text":665},{"id":784,"depth":63,"text":785},{"id":932,"depth":63,"text":933},{"id":1034,"depth":63,"text":1035},{"id":1111,"depth":63,"text":1112},"What the C# compiler actually generates for async\u002Fawait — the state machine, SynchronizationContext, the deadlock pattern that affects ASP.NET apps, and when to use ValueTask over Task.","medium","md",".NET Core","dotnet",{},"\u002Fblog\u002Fdotnet-async-await-state-machine-explained","\u002Fdotnet\u002Fcsharp-core\u002Fasync-await",{"title":5,"description":1162},"blog\u002Fdotnet-async-await-state-machine-explained","Async \u002F Await","C# Core","csharp-core","2026-06-23","HwQ8HT1zkiQsnaVtPGdTqmTSQgSCBeEED1niAH1qZPI",1782244083908]