{"id":2268,"date":"2026-02-14T18:46:06","date_gmt":"2026-02-14T18:46:06","guid":{"rendered":"https:\/\/www.w3computing.com\/articles\/?p=2268"},"modified":"2026-02-14T18:46:12","modified_gmt":"2026-02-14T18:46:12","slug":"async-await-internals-state-machines-allocation-traps-optimizations","status":"publish","type":"post","link":"https:\/\/www.w3computing.com\/articles\/async-await-internals-state-machines-allocation-traps-optimizations\/","title":{"rendered":"Async\/Await Internals: State Machines, Allocation Traps &amp; Optimizations"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Before we dive into IL, builders, and continuation chains, it helps to know exactly what mental model you\u2019re about to construct. This tutorial is not about <em>how<\/em> to use <code>async<\/code>\/<code>await<\/code>\u2014you already do that daily. Instead, we\u2019re going to peel back the abstraction layer and track what actually happens after the compiler rewrites your method. By the end, you\u2019ll be able to look at an async method and predict three things with surprising accuracy: whether it allocates, when it suspends, and where its continuation will run.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We\u2019ll treat async as a system composed of three moving parts: <strong>the compiler-generated state machine<\/strong>, <strong>the runtime scheduling pipeline<\/strong>, and <strong>the allocation surface area<\/strong> those two create together. Each section adds one layer to that model, then validates it with small experiments and measurements so nothing stays theoretical.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You\u2019ll also build a performance intuition toolkit. Instead of guessing why an async method is slow, you\u2019ll learn how to confirm it using benchmarks, allocation metrics, and execution traces. We\u2019ll compare naive implementations against optimized variants and explain <em>why<\/em> the differences appear, not just <em>that<\/em> they do.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Think of this guide as a reverse-engineering walkthrough: starting from a normal async method and progressively revealing the machinery underneath until the abstraction stops being magical and starts being predictable.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. The async\/await contract in modern .NET (what the compiler actually promises)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The biggest misconception about <code>async<\/code>\/<code>await<\/code>\u2014even among experienced developers\u2014is that it \u201cruns code on another thread.\u201d It doesn\u2019t. The compiler never promises parallelism, background execution, or thread switching. What it <em>does<\/em> promise is far more precise: <strong>it will transform your method so that it can pause at await points and resume later without blocking the current thread<\/strong>. That\u2019s the entire contract.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When you mark a method <code>async<\/code>, you\u2019re authorizing the compiler to rewrite it into a form that can suspend execution. Each <code>await<\/code> becomes a checkpoint where control may return to the caller. If the awaited operation is already complete, execution continues synchronously. If not, the method exits early and registers a continuation that will resume it later. No threads are created by this mechanism; it\u2019s purely cooperative suspension.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This leads to an important mental model shift: <strong><code>await<\/code> is not \u201cwait.\u201d It is \u201cschedule continuation if needed.\u201d<\/strong> The distinction matters because performance, allocation behavior, and even deadlock risk all stem from this scheduling step.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Return types define how the outside world observes this process. A method returning <code>Task<\/code> or <code>Task&lt;T&gt;<\/code> represents an operation that may complete in the future. The returned task is essentially a handle to the state machine the compiler generated. A method returning <code>ValueTask&lt;T&gt;<\/code> is similar, but optimized for cases where results are often available synchronously, allowing certain allocations to be avoided. The key nuance is that <code>ValueTask<\/code> is not universally faster\u2014it trades convenience for control, and misusing it can make performance worse rather than better.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So at a contract level, <code>async<\/code> guarantees three things and only three things:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>the method can suspend without blocking<\/li>\n\n\n\n<li>it will resume when its awaited operation completes<\/li>\n\n\n\n<li>its result or exception will flow through its returned task<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Everything else\u2014thread choice, timing, memory cost, scheduling target\u2014is implementation detail. And those implementation details are exactly what we\u2019re about to dissect.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. The compiler rewrite: from <code>await<\/code> to a state machine<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every <code>async<\/code> method you write is quietly replaced by something much more mechanical. The compiler doesn\u2019t execute your method as written\u2014it <strong>translates it into a state machine<\/strong> that can pause and resume itself. Understanding this transformation is the single most important step in mastering async performance and behavior, because nearly every cost or surprise comes from how this generated machine works.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At a high level, your method is split into segments separated by each <code>await<\/code>. Each segment becomes a \u201cstate,\u201d and the compiler generates a hidden struct (or class in some cases) that stores everything needed to resume execution later. That generated type contains:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>a <code>state<\/code> field (integer)<\/li>\n\n\n\n<li>fields for any local variables that must survive across awaits<\/li>\n\n\n\n<li>an async method builder<\/li>\n\n\n\n<li>a <code>MoveNext()<\/code> method that drives execution<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Conceptually, your original method:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">async<\/span> Task&lt;<span class=\"hljs-keyword\">int<\/span>&gt; <span class=\"hljs-title\">ExampleAsync<\/span>(<span class=\"hljs-params\"><\/span>)<\/span>\n{\n    <span class=\"hljs-keyword\">int<\/span> x = <span class=\"hljs-number\">5<\/span>;\n    <span class=\"hljs-keyword\">await<\/span> Task.Delay(<span class=\"hljs-number\">100<\/span>);\n    <span class=\"hljs-keyword\">return<\/span> x * <span class=\"hljs-number\">2<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">is rewritten into something roughly like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">struct<\/span> ExampleStateMachine\n{\n    <span class=\"hljs-keyword\">int<\/span> state;\n    <span class=\"hljs-keyword\">int<\/span> x;\n    AsyncTaskMethodBuilder&lt;<span class=\"hljs-keyword\">int<\/span>&gt; builder;\n    TaskAwaiter awaiter;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">MoveNext<\/span>(<span class=\"hljs-params\"><\/span>)<\/span>\n    {\n        <span class=\"hljs-keyword\">try<\/span>\n        {\n            <span class=\"hljs-keyword\">if<\/span> (state == <span class=\"hljs-number\">0<\/span>)\n                <span class=\"hljs-keyword\">goto<\/span> resume;\n\n            x = <span class=\"hljs-number\">5<\/span>;\n            awaiter = Task.Delay(<span class=\"hljs-number\">100<\/span>).GetAwaiter();\n\n            <span class=\"hljs-keyword\">if<\/span> (!awaiter.IsCompleted)\n            {\n                state = <span class=\"hljs-number\">0<\/span>;\n                builder.AwaitUnsafeOnCompleted(<span class=\"hljs-keyword\">ref<\/span> awaiter, <span class=\"hljs-keyword\">ref<\/span> <span class=\"hljs-keyword\">this<\/span>);\n                <span class=\"hljs-keyword\">return<\/span>;\n            }\n\n        resume:\n            awaiter.GetResult();\n            builder.SetResult(x * <span class=\"hljs-number\">2<\/span>);\n        }\n        <span class=\"hljs-keyword\">catch<\/span> (Exception e)\n        {\n            builder.SetException(e);\n        }\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This isn\u2019t exact output, but structurally it\u2019s very close to what the compiler emits.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why the <code>state<\/code> field exists<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>state<\/code> integer is how the method remembers where it left off. When execution suspends, the state is updated before returning. When the continuation fires later, <code>MoveNext()<\/code> runs again and jumps directly to the correct resume point. In other words, <strong>your method body becomes a resumable switch statement<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Where your locals go (and why that matters)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Any local variable used <em>after<\/em> an await must be preserved. The compiler hoists those locals into fields of the state machine. That means their lifetime is extended from \u201cstack lifetime\u201d to \u201cheap\/struct lifetime.\u201d This has two consequences:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>They can increase memory usage.<\/li>\n\n\n\n<li>They can prevent certain optimizations the JIT would normally apply.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Locals that are only used before an await stay as normal stack variables and disappear once execution suspends. This is why restructuring code to limit cross-await variable usage can actually reduce allocations.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The hidden driver: <code>MoveNext()<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The generated <code>MoveNext()<\/code> method is the real body of your async method. It:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>runs code until an await is encountered<\/li>\n\n\n\n<li>checks whether the awaited operation is complete<\/li>\n\n\n\n<li>schedules continuation if not<\/li>\n\n\n\n<li>resumes execution when continuation fires<\/li>\n\n\n\n<li>completes or faults the returned task<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">You never call <code>MoveNext()<\/code> yourself. The runtime, awaiters, and method builder coordinate to invoke it at exactly the right times.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mental model checkpoint:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An async method is not a method anymore\u2014it\u2019s a tiny object that stores its own progress and knows how to continue itself later.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once you internalize that, async stops being mysterious. It becomes a predictable transformation: <em>sequential code \u2192 resumable state machine.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. Continuations: what <em>actually<\/em> happens at an <code>await<\/code><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">At the source level, <code>await<\/code> looks like a pause. Under the hood, it\u2019s a decision point followed by either a direct continuation or a scheduled one. The compiler expands every <code>await something<\/code> into a pattern built around the awaited object\u2019s <strong>awaiter<\/strong>. That pattern always follows the same sequence:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Call <code>GetAwaiter()<\/code><\/li>\n\n\n\n<li>Check <code>IsCompleted<\/code><\/li>\n\n\n\n<li>If true \u2192 continue synchronously<\/li>\n\n\n\n<li>If false \u2192 register continuation and return<\/li>\n\n\n\n<li>Later \u2192 continuation invokes <code>MoveNext()<\/code><\/li>\n\n\n\n<li>Call <code>GetResult()<\/code><\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">That sequence is the real semantics of <code>await<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The awaiter contract<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Anything can be awaited as long as it provides:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>GetAwaiter()<\/code><\/li>\n\n\n\n<li><code>IsCompleted<\/code><\/li>\n\n\n\n<li><code>OnCompleted(Action)<\/code><\/li>\n\n\n\n<li><code>GetResult()<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This is why <code>Task<\/code>, <code>ValueTask<\/code>, <code>Task.Delay<\/code>, and even custom types can be awaited. They all implement this pattern. The compiler doesn\u2019t care what the type <em>is<\/em>; it only cares that it follows the awaiter contract.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The fast path vs suspend path<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The most important branch in async execution is this check:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">if<\/span> (awaiter.IsCompleted)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">If true, execution continues immediately\u2014no suspension, no continuation registration, often no allocation. This is called the <strong>fast path<\/strong>, and high-performance async code is largely about staying on it as often as possible.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If false, the method must suspend. At that point:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The state is saved.<\/li>\n\n\n\n<li>The continuation (a delegate pointing to <code>MoveNext<\/code>) is registered.<\/li>\n\n\n\n<li>Control returns to the caller.<\/li>\n\n\n\n<li>The method is effectively \u201cparked.\u201d<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">When the awaited operation finishes, it invokes the continuation, which calls <code>MoveNext()<\/code> again. Execution resumes exactly where it left off.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What actually gets scheduled<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Contrary to intuition, the continuation is not the rest of your method. The continuation is <strong>a call back into the state machine<\/strong>. The runtime does not store \u201cremaining lines of code.\u201d It stores:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"has-normal-font-size wp-block-paragraph\">\u201cWhen ready, invoke <code>MoveNext()<\/code> on this instance.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">That\u2019s it. The state machine itself decides what code runs next based on its <code>state<\/code> field.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why this design matters<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This model explains several behaviors that otherwise feel surprising:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Awaiting an already-completed task is extremely cheap.<\/li>\n\n\n\n<li>Awaiting incomplete tasks adds scheduling overhead.<\/li>\n\n\n\n<li>Async performance depends heavily on completion timing.<\/li>\n\n\n\n<li>Most async cost isn\u2019t threading\u2014it\u2019s continuation management.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">It also explains why small structural changes can have large performance effects. Moving an <code>await<\/code>, splitting a method, or avoiding a captured variable can change whether you hit the fast path or suspend.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mental model checkpoint:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>await<\/code> is a branch instruction. It either continues synchronously or registers a callback that re-enters your state machine later.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once you see it that way, async execution becomes something you can reason about\u2014not something you have to trust blindly.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. Scheduling and context capture (the hidden cost center)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Once an <code>await<\/code> decides it <em>can\u2019t<\/em> continue synchronously, the next question becomes: <strong>where should the continuation run?<\/strong> This is where scheduling enters the picture\u2014and where a surprising amount of async overhead lives.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When a suspension happens, the awaiter doesn\u2019t just store \u201ccall <code>MoveNext()<\/code> later.\u201d It also captures information about the <em>current execution environment<\/em>. Specifically, it may capture either:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>a <strong><code>SynchronizationContext<\/code><\/strong> (UI apps, legacy ASP.NET, test frameworks), or<\/li>\n\n\n\n<li>a <strong><code>TaskScheduler<\/code><\/strong> (typically thread pool\u2013based in modern server code).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This captured context determines the thread or environment that will execute the continuation.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why context exists at all<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Certain environments require code to resume on the same logical thread. UI frameworks are the classic example: updating UI controls from a background thread would corrupt state. So when an async method runs inside such a context, the runtime preserves that affinity by capturing it at each await suspension.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In a UI app, the flow looks like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">await<\/span> \u2192 capture UI context \u2192 suspend \u2192 operation finishes \u2192 continuation posted back to UI thread \u2192 MoveNext runs<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">That \u201cpost back\u201d step is real work. It involves scheduling infrastructure, queues, and synchronization. That cost is small individually, but it compounds quickly in high-throughput systems.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The server-side difference<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In modern ASP.NET Core and most backend services, there is <strong>no custom synchronization context<\/strong>. Continuations simply run on whatever thread pool thread becomes available. That means no thread affinity requirement and fewer scheduling steps. As a result, async overhead is typically lower on servers than in UI environments.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is why code copied from UI tutorials often includes <code>ConfigureAwait(false)<\/code> everywhere\u2014it was originally meant to avoid expensive context captures in environments that actually had them.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What <code>ConfigureAwait(false)<\/code> really changes<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Calling:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">await<\/span> task.ConfigureAwait(<span class=\"hljs-literal\">false<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">does <em>not<\/em> make your code faster by magic. It simply tells the awaiter:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"has-normal-font-size wp-block-paragraph\">\u201cDo not capture the current context. Resume on any available thread.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Internally, this skips storing the context reference and avoids scheduling back to it. In environments with a real synchronization context, that can reduce overhead and prevent deadlocks. In environments without one, it usually changes nothing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The key point: <strong><code>ConfigureAwait(false)<\/code> is a scheduling instruction, not a performance switch.<\/strong> Its value depends entirely on where your code runs.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">When you should <em>not<\/em> disable context capture<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If your continuation relies on thread-affine state, skipping capture is a bug. Examples include:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>UI updates<\/li>\n\n\n\n<li>thread-local storage<\/li>\n\n\n\n<li>request-scoped data tied to a context<\/li>\n\n\n\n<li>certain test frameworks<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">In these cases, disabling capture can cause subtle race conditions or outright crashes. The correct rule is not \u201calways use it,\u201d but:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"has-normal-font-size wp-block-paragraph\">Use it when you know you do not require the original execution context.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mental model checkpoint:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An incomplete await doesn\u2019t just pause your method\u2014it packages your state machine together with instructions about <em>where<\/em> it should resume. That scheduling decision is often the most expensive part of async execution, and controlling it intentionally is one of the biggest optimization levers you have.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5. Allocation traps: where the memory actually goes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Most developers assume async overhead is about threads. In reality, the dominant cost in many async-heavy systems is <strong>allocation pressure<\/strong>. The compiler-generated state machine, continuations, captured variables, and task objects can quietly create a steady stream of short-lived allocations that add GC load and reduce throughput. To optimize async code effectively, you need to know exactly <em>which patterns trigger allocations<\/em> and why.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Below are the most common traps that cause unexpected memory churn.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Trap A \u2014 Async methods that suspend allocate<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If an async method completes synchronously, the runtime can often return a cached or stack-based result. But the moment it actually suspends (i.e., <code>IsCompleted == false<\/code>), it must preserve state beyond the current stack frame. That requires allocating or materializing a state machine instance and a continuation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In other words:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"has-normal-font-size wp-block-paragraph\">Async methods only become expensive when they truly go async.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">This is why high-performance libraries try to structure work so common paths complete synchronously.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Trap B \u2014 Locals captured across awaits<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Any variable used before <em>and after<\/em> an await must be hoisted into the state machine. That extends its lifetime and increases memory footprint. Worse, if the variable is a reference type holding large data, the entire object remains alive until the async method completes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example pattern that causes hoisting:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">var<\/span> buffer = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-keyword\">byte<\/span>&#91;<span class=\"hljs-number\">8192<\/span>];\n<span class=\"hljs-keyword\">await<\/span> stream.ReadAsync(buffer);\nProcess(buffer);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Because <code>buffer<\/code> is used after the await, it must be stored in the state machine instead of on the stack.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Trap C \u2014 Async lambdas inside hot paths<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Async lambdas create <strong>closures + state machines<\/strong>. If used inside loops or frequently called methods, they can produce multiple allocations per iteration:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\">items.Select(<span class=\"hljs-keyword\">async<\/span> item =&gt; <span class=\"hljs-keyword\">await<\/span> ProcessAsync(item));<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Each iteration can allocate:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>closure object<\/li>\n\n\n\n<li>state machine<\/li>\n\n\n\n<li>task<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This is one of the most common hidden allocation sources in production async code.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Trap D \u2014 Task.Run layering<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Wrapping async work inside <code>Task.Run<\/code> often creates unnecessary scheduling and allocation layers:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">await<\/span> Task.Run(<span class=\"hljs-keyword\">async<\/span> () =&gt; <span class=\"hljs-keyword\">await<\/span> DoWorkAsync());<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Here you get:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>outer task<\/li>\n\n\n\n<li>inner task<\/li>\n\n\n\n<li>delegate allocation<\/li>\n\n\n\n<li>context switch<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Unless CPU-bound work truly needs offloading, this pattern adds cost without benefit.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Trap E \u2014 Async in tight loops<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Awaiting inside loops can cause per-iteration suspension infrastructure:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">foreach<\/span> (<span class=\"hljs-keyword\">var<\/span> item <span class=\"hljs-keyword\">in<\/span> items)\n{\n    <span class=\"hljs-keyword\">await<\/span> ProcessAsync(item);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Each iteration may allocate continuation machinery. In high-volume loops, this can dominate memory traffic. Batching with <code>Task.WhenAll<\/code> or pipelines often dramatically reduces allocation counts.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">What actually gets allocated?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When suspension occurs, some or all of these objects may be created:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>state machine instance<\/li>\n\n\n\n<li>continuation delegate<\/li>\n\n\n\n<li>task object<\/li>\n\n\n\n<li>closure object<\/li>\n\n\n\n<li>timer (for delays\/timeouts)<\/li>\n\n\n\n<li>boxed structs (rare but possible)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Not every await allocates all of these, but the key takeaway is:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"has-normal-font-size wp-block-paragraph\">Async overhead is compositional. Small costs stack quickly.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Why allocation awareness matters more than micro-optimizations<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A single allocation isn\u2019t a problem. Thousands per second are. In high-throughput systems, allocation rate directly affects:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>GC frequency<\/li>\n\n\n\n<li>latency spikes<\/li>\n\n\n\n<li>cache pressure<\/li>\n\n\n\n<li>throughput stability<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This is why elite .NET performance tuning often focuses more on reducing allocations than reducing CPU instructions.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mental model checkpoint:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Async performance isn\u2019t about avoiding async\u2014it\u2019s about avoiding unnecessary suspension and unnecessary captured state. The fastest async method is one that either completes synchronously or suspends with minimal state to preserve.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">6. State machine optimizations you can deliberately trigger<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Once you understand that async methods compile into state machines, optimization stops being guesswork. You\u2019re no longer trying to \u201cmake async faster\u201d\u2014you\u2019re trying to <strong>influence what the compiler generates<\/strong> and <strong>minimize the work that state machine must preserve<\/strong>. The goal is simple: suspend less often, and when suspension is unavoidable, carry less state.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Optimization 1 \u2014 Favor synchronous completion on common paths<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The cheapest async method is one that never suspends. If a frequently executed path can complete synchronously, structure it so the awaiter reports <code>IsCompleted == true<\/code>. Many high-performance APIs are designed specifically for this pattern (e.g., caches, buffered reads, pooled objects).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example strategy:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>check fast cache first<\/li>\n\n\n\n<li>only await when cache miss occurs<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This keeps hot paths allocation-free while still supporting async behavior when needed.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Optimization 2 \u2014 Reduce cross-await variable lifetimes<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Any local used after an await must be hoisted into the state machine. That means you can often shrink the state machine just by narrowing variable scope:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">var<\/span> data = <span class=\"hljs-keyword\">await<\/span> LoadAsync();\n<span class=\"hljs-keyword\">return<\/span> Transform(data);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">You can sometimes restructure:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">return<\/span> Transform(<span class=\"hljs-keyword\">await<\/span> LoadAsync());<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Now <code>data<\/code> never exists as a hoisted field. Small change, measurable difference in tight paths.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">General rule:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"has-normal-font-size wp-block-paragraph\">Variables that don\u2019t cross awaits don\u2019t get hoisted.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Optimization 3 \u2014 Avoid capturing <code>this<\/code> unintentionally<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Instance methods implicitly capture the current object. If the async method suspends, the state machine holds a reference to <code>this<\/code> until completion. That can keep large object graphs alive longer than intended.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Mitigations:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>move logic to static helper methods<\/li>\n\n\n\n<li>pass only required data as parameters<\/li>\n\n\n\n<li>avoid referencing fields unnecessarily<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This is especially important in long-running async operations.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Optimization 4 \u2014 Remove unnecessary async wrappers<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If a method simply returns another task, marking it <code>async<\/code> adds a state machine for no reason.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Avoid:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">async<\/span> Task&lt;<span class=\"hljs-keyword\">int<\/span>&gt; <span class=\"hljs-title\">GetAsync<\/span>(<span class=\"hljs-params\"><\/span>)<\/span>\n{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">await<\/span> repository.FetchAsync();\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Prefer:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Task&lt;<span class=\"hljs-keyword\">int<\/span>&gt; <span class=\"hljs-title\">GetAsync<\/span>(<span class=\"hljs-params\"><\/span>)<\/span>\n{\n    <span class=\"hljs-keyword\">return<\/span> repository.FetchAsync();\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The second version eliminates the generated state machine entirely. This is one of the highest-impact zero-cost optimizations.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Optimization 5 \u2014 Use <code>ValueTask<\/code> only when it actually helps<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>ValueTask&lt;T&gt;<\/code> can avoid allocations when results are often synchronous, but it introduces complexity:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>it can only be awaited once<\/li>\n\n\n\n<li>it must not be stored casually<\/li>\n\n\n\n<li>it complicates composition<\/li>\n\n\n\n<li>it increases caller responsibility<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">It\u2019s beneficial when:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>synchronous completion is common<\/li>\n\n\n\n<li>the method is hot-path<\/li>\n\n\n\n<li>profiling shows task allocation cost matters<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">It\u2019s harmful when used indiscriminately. Treat it as a precision tool, not a default.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Optimization 6 \u2014 Minimize continuation weight<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Every suspension registers a continuation delegate. If that continuation captures large state or closures, you increase allocation and memory retention cost.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Prefer patterns where continuations:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>capture minimal data<\/li>\n\n\n\n<li>reference structs or primitives where possible<\/li>\n\n\n\n<li>avoid lambdas in hot paths<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Even small reductions in captured state can significantly lower allocation rates under load.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Optimization 7 \u2014 Design for suspension boundaries<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Async performance often improves when you intentionally choose where suspension is allowed. For example:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>batch I\/O before awaiting<\/li>\n\n\n\n<li>combine tasks with <code>WhenAll<\/code><\/li>\n\n\n\n<li>avoid awaits inside tight loops<\/li>\n\n\n\n<li>split large async methods into smaller ones<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">These patterns reduce the number of times the state machine must pause and resume.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mental model checkpoint:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You don\u2019t optimize async by \u201cwriting faster code.\u201d You optimize it by shaping the state machine the compiler generates. Smaller state + fewer suspensions = less allocation + less scheduling + better throughput.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">7. Performance measurement: proving it with BenchmarkDotNet<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Understanding async internals is useful, but performance intuition only becomes reliable when you can <em>measure<\/em> what\u2019s happening. Async behavior can be counterintuitive\u2014changes that look trivial in source code can drastically affect allocations or latency. That\u2019s why serious async tuning always involves benchmarking. Tools like <strong>BenchmarkDotNet<\/strong> let you validate assumptions and observe exactly how suspension, continuations, and allocations behave under load.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">What you should actually measure<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When profiling async code, raw execution time isn\u2019t enough. You want a combination of metrics:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Allocated bytes\/op<\/strong> \u2192 shows hidden state machine or closure allocations<\/li>\n\n\n\n<li><strong>Gen0\/Gen1 collections<\/strong> \u2192 indicates GC pressure<\/li>\n\n\n\n<li><strong>Mean time<\/strong> \u2192 average performance<\/li>\n\n\n\n<li><strong>P95\/P99 latency<\/strong> \u2192 reveals scheduling stalls or contention<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Allocation metrics are especially important because async overhead often shows up as memory churn rather than CPU usage.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">A simple baseline experiment<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Start with two versions of the same method:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Version A: naive async<\/li>\n\n\n\n<li>Version B: optimized structure<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Example idea:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>one method awaits inside a loop<\/li>\n\n\n\n<li>another batches tasks and awaits once<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Your benchmark harness should call each thousands of times so small per-call costs become visible. Async overhead often looks negligible at small scales but becomes obvious under repetition.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Reading the results correctly<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Common patterns you\u2019ll notice:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Higher allocations usually correlate with slower throughput.<\/li>\n\n\n\n<li>Removing <code>async<\/code> wrappers can eliminate entire allocations.<\/li>\n\n\n\n<li>Synchronous completion paths often show <em>zero<\/em> allocations.<\/li>\n\n\n\n<li><code>ValueTask<\/code> helps only when suspension is rare.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">One key insight: <strong>if allocations drop but execution time doesn\u2019t<\/strong>, that\u2019s still a win. Lower allocation rates reduce GC frequency, which improves latency stability under real load.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Benchmarking mistakes developers make<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Async benchmarks are easy to get wrong. Watch out for these:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Not awaiting the task<\/strong> \u2192 measures scheduling, not execution<\/li>\n\n\n\n<li><strong>Running too few iterations<\/strong> \u2192 hides allocation patterns<\/li>\n\n\n\n<li><strong>Benchmarking cold paths only<\/strong> \u2192 misses realistic workload behavior<\/li>\n\n\n\n<li><strong>Mixing CPU-bound and I\/O-bound tests<\/strong> \u2192 produces misleading comparisons<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Another common mistake is assuming the thread pool behaves the same in benchmarks as in production. In reality, thread pool heuristics, environment load, and timing all influence async scheduling. Benchmarks should therefore be treated as <em>comparative tools<\/em>, not absolute performance guarantees.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">What good async benchmarking teaches you<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">After running a few experiments, patterns start to emerge:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>suspension is expensive relative to synchronous completion<\/li>\n\n\n\n<li>closures are often more costly than awaits<\/li>\n\n\n\n<li>structure matters more than syntax<\/li>\n\n\n\n<li>removing unnecessary awaits can outperform micro-optimizations<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This is where theory turns into intuition. Once you\u2019ve seen how small structural changes affect allocation counts and latency graphs, you stop guessing and start predicting.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mental model checkpoint:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Async optimization isn\u2019t about memorizing rules\u2014it\u2019s about forming hypotheses and testing them. Benchmarking is how you confirm whether a change actually improves the generated state machine and runtime behavior, instead of just looking cleaner in code.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">8. Practical refactors (before\/after patterns you can apply immediately)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Theory is useful, but async performance gains usually come from a handful of repeatable structural refactors. Once you recognize these patterns, you\u2019ll start spotting them everywhere\u2014in services, libraries, APIs, background workers, even UI code. The following examples are intentionally simple so the mechanics are obvious, but these same transformations routinely produce measurable gains in production systems.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 1 \u2014 Remove unnecessary <code>async<\/code> wrappers<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Before<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">async<\/span> Task&lt;User&gt; <span class=\"hljs-title\">GetUserAsync<\/span>(<span class=\"hljs-params\"><span class=\"hljs-keyword\">int<\/span> id<\/span>)<\/span>\n{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">await<\/span> repository.GetAsync(id);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>After<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Task&lt;User&gt; <span class=\"hljs-title\">GetUserAsync<\/span>(<span class=\"hljs-params\"><span class=\"hljs-keyword\">int<\/span> id<\/span>)<\/span>\n{\n    <span class=\"hljs-keyword\">return<\/span> repository.GetAsync(id);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Why it helps: the first version generates a state machine and continuation. The second returns the existing task directly\u2014no extra allocation, no extra scheduling.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Rule of thumb:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"has-normal-font-size wp-block-paragraph\">If all you do is <code>return await<\/code>, you probably don\u2019t need <code>async<\/code>.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 2 \u2014 Collapse nested awaits<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Before<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">var<\/span> result = <span class=\"hljs-keyword\">await<\/span> (<span class=\"hljs-keyword\">await<\/span> client.GetAsync(url)).Content.ReadAsStringAsync();<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>After<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">var<\/span> response = <span class=\"hljs-keyword\">await<\/span> client.GetAsync(url);\n<span class=\"hljs-keyword\">var<\/span> result = <span class=\"hljs-keyword\">await<\/span> response.Content.ReadAsStringAsync();<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This isn\u2019t just readability. The rewritten version often produces a simpler state machine because fewer temporaries must be preserved across await boundaries. Cleaner structure frequently means fewer hoisted fields.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 3 \u2014 Batch instead of serial await<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Before (serial)<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">foreach<\/span> (<span class=\"hljs-keyword\">var<\/span> id <span class=\"hljs-keyword\">in<\/span> ids)\n{\n    <span class=\"hljs-keyword\">await<\/span> LoadAsync(id);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>After (batched)<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">await<\/span> Task.WhenAll(ids.Select(LoadAsync));<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Why it helps:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>fewer suspension points<\/li>\n\n\n\n<li>fewer continuations<\/li>\n\n\n\n<li>better parallelism<\/li>\n\n\n\n<li>reduced scheduling overhead<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Serial awaits force the state machine to pause and resume repeatedly. Batching lets you suspend once while many operations complete concurrently.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 4 \u2014 Replace async lambdas in hot paths<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Before<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">await<\/span> Task.WhenAll(items.Select(<span class=\"hljs-keyword\">async<\/span> i =&gt; <span class=\"hljs-keyword\">await<\/span> ProcessAsync(i)));<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>After<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">await<\/span> Task.WhenAll(items.Select(ProcessAsync));<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The first version creates an async lambda state machine per element. The second reuses an existing method group and avoids those allocations entirely.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 5 \u2014 Avoid per-item async in streams<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A common performance trap is async work per element:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Before<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">foreach<\/span> (<span class=\"hljs-keyword\">var<\/span> item <span class=\"hljs-keyword\">in<\/span> source)\n{\n    <span class=\"hljs-keyword\">await<\/span> ProcessAsync(item);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This can create a suspension for every item.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Optimized approach<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>buffer items<\/li>\n\n\n\n<li>process in batches<\/li>\n\n\n\n<li>await once per batch<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Batching reduces continuation registrations and dramatically lowers allocation counts for large streams.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 6 \u2014 Hoist invariant work out of async paths<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If something doesn\u2019t depend on awaited results, compute it before the first await:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Before<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">await<\/span> networkCall;\n<span class=\"hljs-keyword\">var<\/span> hash = ComputeHash(config);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>After<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">var<\/span> hash = ComputeHash(config);\n<span class=\"hljs-keyword\">await<\/span> networkCall;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Now <code>hash<\/code> doesn\u2019t cross an await boundary, so it doesn\u2019t become a state-machine field.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 7 \u2014 Split large async methods<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Long async methods with many awaits create large state machines. Splitting them into smaller methods can:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>shrink state size<\/li>\n\n\n\n<li>reduce hoisted locals<\/li>\n\n\n\n<li>improve JIT optimization<\/li>\n\n\n\n<li>improve readability<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Think of it as reducing the \u201cpayload\u201d each suspension must carry.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h4 class=\"wp-block-heading\">Why these patterns work<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">All of these transformations improve performance for the same underlying reason:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"has-normal-font-size wp-block-paragraph\">They reduce what the state machine must store or how often it must suspend.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Async performance is structural. Small layout changes can remove entire allocations or eliminate scheduling steps. Once you recognize that the compiler is generating a resumable object behind the scenes, these optimizations stop feeling like tricks and start feeling obvious.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mental model checkpoint:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When optimizing async code, you\u2019re not rewriting logic\u2014you\u2019re reshaping the hidden state machine. Cleaner structure usually means a smaller machine, fewer continuations, and less memory pressure.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">9. Advanced corner cases (the ones that surprise even senior developers)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Once you understand state machines, continuations, and scheduling, most async behavior becomes predictable. But there are still edge cases where intuition fails\u2014usually because subtle runtime rules interact in ways that aren\u2019t obvious from source code. These are the scenarios where deep async knowledge stops being academic and starts preventing real production bugs.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Deadlocks that <em>shouldn\u2019t<\/em> happen \u2014 but do<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Classic async deadlocks usually involve blocking on an async result:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">var<\/span> result = GetDataAsync().Result;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Why this can deadlock:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Caller thread blocks waiting for result.<\/li>\n\n\n\n<li>Async method suspends.<\/li>\n\n\n\n<li>Continuation tries to resume on captured context.<\/li>\n\n\n\n<li>Context thread is blocked.<\/li>\n\n\n\n<li>Continuation never runs.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">This isn\u2019t an async problem\u2014it\u2019s a scheduling problem. The continuation is ready, but the only thread allowed to run it is unavailable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The underlying rule:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"has-normal-font-size wp-block-paragraph\">Blocking + captured context + single-threaded scheduler = deadlock risk.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">This is why <code>.Result<\/code> and <code>.Wait()<\/code> are dangerous in environments with synchronization contexts.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><code>async void<\/code> isn\u2019t just \u201cbad\u201d\u2014it changes failure semantics<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Most developers know to avoid <code>async void<\/code>, but fewer understand <em>why<\/em>. The issue isn\u2019t style; it\u2019s runtime behavior.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An <code>async Task<\/code> method reports exceptions through its returned task. An <code>async void<\/code> method has no task, so exceptions propagate directly to the synchronization context or process-level handler. That means:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>callers can\u2019t await it<\/li>\n\n\n\n<li>callers can\u2019t catch its exceptions<\/li>\n\n\n\n<li>failures may crash the process<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The real distinction:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"has-normal-font-size wp-block-paragraph\"><code>async Task<\/code> = composable operation<br><code>async void<\/code> = fire-and-forget event handler<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">There is exactly one legitimate use case: event handlers that must match a void signature.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Cancellation doesn\u2019t behave like most people think<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Cancellation tokens don\u2019t cancel work automatically. They\u2019re cooperative signals. The runtime does not interrupt your method\u2014you must explicitly check or pass the token into operations that respect it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Also, cancellation is represented as an <strong>exception<\/strong> (<code>OperationCanceledException<\/code>). This is intentional: it allows cancellation to flow through async call chains the same way failures do. But it also means sloppy exception handling can accidentally swallow cancellations and make operations appear to succeed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Subtle but important distinction:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>timeout = external decision<\/li>\n\n\n\n<li>cancellation = cooperative request<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Treating them as identical often leads to confusing logic.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Exception timing depends on await boundaries<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Where an exception surfaces depends on whether a method has reached an await yet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-function\">Task <span class=\"hljs-title\">FooAsync<\/span>(<span class=\"hljs-params\"><\/span>)<\/span>\n{\n    <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> Exception();\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This throws immediately when called.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">async<\/span> Task <span class=\"hljs-title\">FooAsync<\/span>(<span class=\"hljs-params\"><\/span>)<\/span>\n{\n    <span class=\"hljs-keyword\">await<\/span> Task.Yield();\n    <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> Exception();\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This does <strong>not<\/strong> throw immediately. Instead:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>method returns a faultable task<\/li>\n\n\n\n<li>exception appears only when awaited<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This difference matters when composing tasks, writing retry logic, or debugging failures. The rule:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"has-normal-font-size wp-block-paragraph\">Before first await \u2192 synchronous execution<br>After first await \u2192 asynchronous execution<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Fire-and-forget tasks and lost failures<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Starting tasks without awaiting them can silently drop exceptions:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\">_ = DoWorkAsync();<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">If the task faults and no one observes it, the runtime may only surface it during finalization\u2014or not at all depending on configuration. Production systems have shipped with bugs that ran for months because a background task failed silently once and never retried.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Safer pattern:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>explicitly track background tasks<\/li>\n\n\n\n<li>attach logging continuations<\/li>\n\n\n\n<li>use hosted service patterns or schedulers<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Fire-and-forget is not inherently wrong\u2014but it must be intentional and monitored.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Async + locks = subtle contention<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Mixing synchronous locks with async can serialize execution unexpectedly:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">lock<\/span> (_gate)\n{\n    <span class=\"hljs-keyword\">await<\/span> WorkAsync();\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This doesn\u2019t even compile\u2014but replacing <code>lock<\/code> with <code>SemaphoreSlim<\/code> often introduces hidden bottlenecks if used incorrectly. Async-friendly synchronization primitives must still be used carefully to avoid turning concurrency into accidental serialization.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mental model checkpoint:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Most async \u201cmysteries\u201d aren\u2019t mysteries\u2014they\u2019re interactions between scheduling rules, continuation timing, and exception flow. When you understand those three axes, edge cases stop being surprising and start being diagnosable.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">10. Final checklist: async fast-path rules you can apply in seconds<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">By now you\u2019ve seen how async methods are compiled, scheduled, suspended, resumed, and measured. The goal of this final section is not to introduce anything new, but to compress all of that knowledge into a <strong>practical diagnostic checklist<\/strong>. Think of this as a mental profiler you can run in your head whenever you read or write async code. Experienced developers rarely analyze async line by line\u2014they scan for structural signals that predict performance, allocation behavior, and correctness risks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Below is that scan list.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">The 12-second async evaluation checklist<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When you see an async method, ask:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>1. Will it usually complete synchronously?<\/strong><br>If yes \u2192 likely allocation-free fast path.<br>If no \u2192 expect state machine + continuation cost.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>2. Does it really need <code>async<\/code>?<\/strong><br>If it only returns another task, remove <code>async<\/code> and return directly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>3. Which variables cross awaits?<\/strong><br>Those become state machine fields. Fewer = smaller state machine.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>4. Is context capture necessary?<\/strong><br>If not, skipping it may reduce scheduling overhead.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>5. Are awaits inside loops?<\/strong><br>Repeated suspension can dominate runtime cost.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>6. Are async lambdas used in hot paths?<\/strong><br>Expect closure + task allocations per invocation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>7. Is <code>ValueTask<\/code> justified?<\/strong><br>Use only if synchronous completion is common <em>and measured<\/em>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>8. Could operations be batched?<\/strong><br>One suspension is cheaper than many.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>9. Are tasks being blocked on?<\/strong><br>Blocking + captured context = deadlock risk.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>10. Are exceptions observable?<\/strong><br>Fire-and-forget tasks can hide failures.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>11. Does the method hold large objects across awaits?<\/strong><br>That extends their lifetime unnecessarily.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>12. Is this path performance-critical?<\/strong><br>If yes \u2192 benchmark before and after changes.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">The three rules that matter most<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If you remember nothing else from this tutorial, remember these:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"has-normal-font-size wp-block-paragraph\"><strong>Rule 1:<\/strong> Async isn\u2019t expensive. Unnecessary suspension is.<br><strong>Rule 2:<\/strong> Structure determines performance more than syntax.<br><strong>Rule 3:<\/strong> Measure before optimizing.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">These three principles explain nearly every async performance outcome you\u2019ll encounter in real systems.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">A final mental model<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">An async method is best thought of as a <strong>self-contained resumable object<\/strong> that:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>stores its own state<\/li>\n\n\n\n<li>schedules its own continuation<\/li>\n\n\n\n<li>completes its own promise<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Performance problems arise when that object becomes too large, suspends too often, or schedules inefficiently. Optimization is simply the act of shrinking it, simplifying it, or suspending it less.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once you see async this way, you stop treating it as magic and start treating it as machinery. And machinery can be inspected, reasoned about, and improved.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>End takeaway:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You don\u2019t need to memorize compiler output or IL to master async. You just need a clear mental picture of what\u2019s generated, what\u2019s allocated, and what\u2019s scheduled. With that model in place, async code stops being unpredictable\u2014and becomes something you can deliberately shape for correctness, scalability, and speed.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Before we dive into IL, builders, and continuation chains, it helps to know exactly what mental model you\u2019re about to construct. This tutorial is not about how to use async\/await\u2014you already do that daily. Instead, we\u2019re going to peel back the abstraction layer and track what actually happens after the compiler rewrites your method. By [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_genesis_hide_title":false,"_genesis_hide_breadcrumbs":false,"_genesis_hide_singular_image":false,"_genesis_hide_footer_widgets":false,"_genesis_custom_body_class":"","_genesis_custom_post_class":"","_genesis_layout":"","_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[8,4],"tags":[],"class_list":["post-2268","post","type-post","status-publish","format-standard","category-csharp","category-programming-languages","entry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.6 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Async\/Await Internals: State Machines, Allocation Traps &amp; Optimizations<\/title>\n<meta name=\"description\" content=\"Before we dive into IL, builders, and continuation chains, it helps to know exactly what mental model you\u2019re about to construct.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.w3computing.com\/articles\/async-await-internals-state-machines-allocation-traps-optimizations\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Async\/Await Internals: State Machines, Allocation Traps &amp; Optimizations\" \/>\n<meta property=\"og:description\" content=\"Before we dive into IL, builders, and continuation chains, it helps to know exactly what mental model you\u2019re about to construct.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.w3computing.com\/articles\/async-await-internals-state-machines-allocation-traps-optimizations\/\" \/>\n<meta property=\"article:published_time\" content=\"2026-02-14T18:46:06+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-02-14T18:46:12+00:00\" \/>\n<meta name=\"author\" content=\"w3compadmin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"w3compadmin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"21 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"TechArticle\",\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/async-await-internals-state-machines-allocation-traps-optimizations\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/async-await-internals-state-machines-allocation-traps-optimizations\\\/\"},\"author\":{\"name\":\"w3compadmin\",\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/#\\\/schema\\\/person\\\/a550b3e20d78bb4f79b7c6b7b53f0561\"},\"headline\":\"Async\\\/Await Internals: State Machines, Allocation Traps &amp; Optimizations\",\"datePublished\":\"2026-02-14T18:46:06+00:00\",\"dateModified\":\"2026-02-14T18:46:12+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/async-await-internals-state-machines-allocation-traps-optimizations\\\/\"},\"wordCount\":4754,\"articleSection\":[\"C#\",\"Programming Languages\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/async-await-internals-state-machines-allocation-traps-optimizations\\\/\",\"url\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/async-await-internals-state-machines-allocation-traps-optimizations\\\/\",\"name\":\"Async\\\/Await Internals: State Machines, Allocation Traps &amp; Optimizations\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/#website\"},\"datePublished\":\"2026-02-14T18:46:06+00:00\",\"dateModified\":\"2026-02-14T18:46:12+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/#\\\/schema\\\/person\\\/a550b3e20d78bb4f79b7c6b7b53f0561\"},\"description\":\"Before we dive into IL, builders, and continuation chains, it helps to know exactly what mental model you\u2019re about to construct.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/async-await-internals-state-machines-allocation-traps-optimizations\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/async-await-internals-state-machines-allocation-traps-optimizations\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/async-await-internals-state-machines-allocation-traps-optimizations\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Articles Home\",\"item\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Programming Languages\",\"item\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/programming-languages\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"C#\",\"item\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/programming-languages\\\/csharp\\\/\"},{\"@type\":\"ListItem\",\"position\":4,\"name\":\"Async\\\/Await Internals: State Machines, Allocation Traps &amp; Optimizations\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/#website\",\"url\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/\",\"name\":\"Developer Articles Hub\",\"description\":\"\",\"alternateName\":\"Developer Articles\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/#\\\/schema\\\/person\\\/a550b3e20d78bb4f79b7c6b7b53f0561\",\"name\":\"w3compadmin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/wp-content\\\/litespeed\\\/avatar\\\/bd481d404e42caa2763662a3bfe825f8.jpg?ver=1780141266\",\"url\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/wp-content\\\/litespeed\\\/avatar\\\/bd481d404e42caa2763662a3bfe825f8.jpg?ver=1780141266\",\"contentUrl\":\"https:\\\/\\\/www.w3computing.com\\\/articles\\\/wp-content\\\/litespeed\\\/avatar\\\/bd481d404e42caa2763662a3bfe825f8.jpg?ver=1780141266\",\"caption\":\"w3compadmin\"},\"sameAs\":[\"http:\\\/\\\/w3computing.com\\\/articles\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Async\/Await Internals: State Machines, Allocation Traps &amp; Optimizations","description":"Before we dive into IL, builders, and continuation chains, it helps to know exactly what mental model you\u2019re about to construct.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.w3computing.com\/articles\/async-await-internals-state-machines-allocation-traps-optimizations\/","og_locale":"en_US","og_type":"article","og_title":"Async\/Await Internals: State Machines, Allocation Traps &amp; Optimizations","og_description":"Before we dive into IL, builders, and continuation chains, it helps to know exactly what mental model you\u2019re about to construct.","og_url":"https:\/\/www.w3computing.com\/articles\/async-await-internals-state-machines-allocation-traps-optimizations\/","article_published_time":"2026-02-14T18:46:06+00:00","article_modified_time":"2026-02-14T18:46:12+00:00","author":"w3compadmin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"w3compadmin","Est. reading time":"21 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"TechArticle","@id":"https:\/\/www.w3computing.com\/articles\/async-await-internals-state-machines-allocation-traps-optimizations\/#article","isPartOf":{"@id":"https:\/\/www.w3computing.com\/articles\/async-await-internals-state-machines-allocation-traps-optimizations\/"},"author":{"name":"w3compadmin","@id":"https:\/\/www.w3computing.com\/articles\/#\/schema\/person\/a550b3e20d78bb4f79b7c6b7b53f0561"},"headline":"Async\/Await Internals: State Machines, Allocation Traps &amp; Optimizations","datePublished":"2026-02-14T18:46:06+00:00","dateModified":"2026-02-14T18:46:12+00:00","mainEntityOfPage":{"@id":"https:\/\/www.w3computing.com\/articles\/async-await-internals-state-machines-allocation-traps-optimizations\/"},"wordCount":4754,"articleSection":["C#","Programming Languages"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/www.w3computing.com\/articles\/async-await-internals-state-machines-allocation-traps-optimizations\/","url":"https:\/\/www.w3computing.com\/articles\/async-await-internals-state-machines-allocation-traps-optimizations\/","name":"Async\/Await Internals: State Machines, Allocation Traps &amp; Optimizations","isPartOf":{"@id":"https:\/\/www.w3computing.com\/articles\/#website"},"datePublished":"2026-02-14T18:46:06+00:00","dateModified":"2026-02-14T18:46:12+00:00","author":{"@id":"https:\/\/www.w3computing.com\/articles\/#\/schema\/person\/a550b3e20d78bb4f79b7c6b7b53f0561"},"description":"Before we dive into IL, builders, and continuation chains, it helps to know exactly what mental model you\u2019re about to construct.","breadcrumb":{"@id":"https:\/\/www.w3computing.com\/articles\/async-await-internals-state-machines-allocation-traps-optimizations\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.w3computing.com\/articles\/async-await-internals-state-machines-allocation-traps-optimizations\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.w3computing.com\/articles\/async-await-internals-state-machines-allocation-traps-optimizations\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Articles Home","item":"https:\/\/www.w3computing.com\/articles\/"},{"@type":"ListItem","position":2,"name":"Programming Languages","item":"https:\/\/www.w3computing.com\/articles\/programming-languages\/"},{"@type":"ListItem","position":3,"name":"C#","item":"https:\/\/www.w3computing.com\/articles\/programming-languages\/csharp\/"},{"@type":"ListItem","position":4,"name":"Async\/Await Internals: State Machines, Allocation Traps &amp; Optimizations"}]},{"@type":"WebSite","@id":"https:\/\/www.w3computing.com\/articles\/#website","url":"https:\/\/www.w3computing.com\/articles\/","name":"Developer Articles Hub","description":"","alternateName":"Developer Articles","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.w3computing.com\/articles\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.w3computing.com\/articles\/#\/schema\/person\/a550b3e20d78bb4f79b7c6b7b53f0561","name":"w3compadmin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.w3computing.com\/articles\/wp-content\/litespeed\/avatar\/bd481d404e42caa2763662a3bfe825f8.jpg?ver=1780141266","url":"https:\/\/www.w3computing.com\/articles\/wp-content\/litespeed\/avatar\/bd481d404e42caa2763662a3bfe825f8.jpg?ver=1780141266","contentUrl":"https:\/\/www.w3computing.com\/articles\/wp-content\/litespeed\/avatar\/bd481d404e42caa2763662a3bfe825f8.jpg?ver=1780141266","caption":"w3compadmin"},"sameAs":["http:\/\/w3computing.com\/articles"]}]}},"featured_image_src":null,"featured_image_src_square":null,"author_info":{"display_name":"w3compadmin","author_link":"https:\/\/www.w3computing.com\/articles\/author\/w3compadmin\/"},"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/posts\/2268","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/comments?post=2268"}],"version-history":[{"count":11,"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/posts\/2268\/revisions"}],"predecessor-version":[{"id":2279,"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/posts\/2268\/revisions\/2279"}],"wp:attachment":[{"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/media?parent=2268"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/categories?post=2268"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.w3computing.com\/articles\/wp-json\/wp\/v2\/tags?post=2268"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}