{"authors":[{"name":"Mayuresh Waykole","url":"https://mayureshwaykole.com/"}],"description":"Engineering writing by Mayuresh Waykole on distributed systems, observability, reliability, and AI engineering.","favicon":"https://mayureshwaykole.com/favicon-32x32.png","feed_url":"https://mayureshwaykole.com/tags/thread-starvation/feed.json","home_page_url":"https://mayureshwaykole.com/","icon":"https://mayureshwaykole.com/apple-touch-icon.png","image":"https://mayureshwaykole.com/images/social-preview.png","items":[{"authors":[{"name":"Mayuresh Waykole"}],"content_html":"\u003cp\u003eRecently we\u0026rsquo;ve seen an abundance of async-only APIs in C# and common libraries like HttpClient.\nIn most cases, this encourages async/await best practices, especially on the server side, but there are exceptions.\nIn some situations, we need to call these async methods synchronously.\u003c/p\u003e\n\u003cp\u003eOne common way to do this is \u003ccode\u003eGetAwaiter().GetResult()\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eTask\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003estring\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e pingTask \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e new \u003cspan style=\"color:#a6e22e\"\u003eHttpClient\u003c/span\u003e().\u003cspan style=\"color:#a6e22e\"\u003eGetStringAsync\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://google.com\u0026#34;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003evar webpage \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pingTask.\u003cspan style=\"color:#a6e22e\"\u003eGetAwaiter\u003c/span\u003e().\u003cspan style=\"color:#a6e22e\"\u003eGetResult\u003c/span\u003e();\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe \u003ccode\u003eGetAwaiter().GetResult()\u003c/code\u003e call blocks the calling thread and this can cause thread starvation.\u003c/p\u003e\n\u003cp\u003eConsider an example where you want to fetch a few webpages in parallel.\nFor simplicity, we will just call \u003ccode\u003egoogle.com\u003c/code\u003e.\nThe function looks like this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-c#\" data-lang=\"c#\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e/// \u0026lt;summary\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e/// Synchronously pings the URL and returns the result.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e/// \u0026lt;/summary\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e/// \u0026lt;returns\u0026gt;Webpage content\u0026lt;/returns\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eprivate\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estatic\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e PingUrlSync()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// Call the asynchronous method and wait for the result\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    Task\u0026lt;\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e\u0026gt; pingTask = \u003cspan style=\"color:#66d9ef\"\u003enew\u003c/span\u003e HttpClient().GetStringAsync(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://google.com\u0026#34;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e webpage = pingTask.GetAwaiter().GetResult();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e webpage;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAt first glance, this looks fine. If we run this once, we\u0026rsquo;re sure to get the webpage string.\nIf we want to run this a few times in parallel, we\u0026rsquo;d do something like this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-c#\" data-lang=\"c#\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// A randomly high number of iterations\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e taskCount = \u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Start multiple tasks to ping the URL\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e i = \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e; i \u0026lt; taskCount; i++)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    tasks.Add(Task.Run(() =\u0026gt; { PingUrlSync(); }));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis is where the problem starts.\nIf we add some logging and constrain the .NET thread pool so the number of worker threads matches the number of processors, this code will hang.\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode class=\"language-log\" data-lang=\"log\"\u003eprocessor count: 8\nMax worker threads set to 17\nMax completion port threads set to 8\nPingUrlSync\t, Iteration: 0, ThreadId: 4\nPingUrlSync\t, Iteration: 1, ThreadId: 3\nPingUrlSync\t, Iteration: 3, ThreadId: 8\nPingUrlSync\t, Iteration: 2, ThreadId: 7\nPingUrlSync\t, Iteration: 4, ThreadId: 9\nPingUrlSync\t, Iteration: 5, ThreadId: 10\nPingUrlSync\t, Iteration: 6, ThreadId: 11\nPingUrlSync\t, Iteration: 7, ThreadId: 12\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eIn this post, we\u0026rsquo;ll understand why this code hangs and how to investigate such issues in Visual Studio.\u003c/p\u003e\n\u003ch2 id=\"task-parallel-library\"\u003eTask Parallel Library\u003c/h2\u003e\n\u003cp\u003eWe need a little background on the Task Parallel Library to understand what\u0026rsquo;s going on.\u003c/p\u003e\n\u003cp\u003eTask Parallel Library (TPL) is a set of public types and APIs in .NET that simplify the process of adding parallelism and concurrency to applications. It was introduced in .NET Framework 4.0 and is the recommended way to write multithreaded and parallel code.\u003c/p\u003e\n\u003cp\u003eTPL handles the partitioning of work, scheduling of threads on the ThreadPool, cancellation support, state management, and other low-level details. It scales the degree of concurrency dynamically to most efficiently use all available processors.\u003c/p\u003e\n\u003cp\u003eWhen running async/await or threaded tasks, all these tasks get pushed to a queue and are scheduled on the thread pool.\u003c/p\u003e\n\u003ch3 id=\"asyncawait\"\u003eAsync/Await\u003c/h3\u003e\n\u003cp\u003eEssentially, async/await code is internally a set of tasks.\u003c/p\u003e\n\u003cp\u003eWhen you call an async method, it returns a task that represents the I/O work.\nThe return value is a task with a return type.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eTask\u0026lt;string\u0026gt; pingTask = new HttpClient().GetStringAsync(\u0026quot;https://google.com\u0026quot;);\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eWhen you call \u003ccode\u003eawait\u003c/code\u003e on this task, the runtime frees the calling thread and schedules the rest of your code as a continuation.\nThat continuation runs only after the awaited task is complete.\u003c/p\u003e\n\u003ch2 id=\"thread-starvation\"\u003eThread Starvation\u003c/h2\u003e\n\u003cp\u003eNow that we have background on the problem and some basics about the Task Parallel Library, we can dig deeper.\u003c/p\u003e\n\u003cp\u003eWhen we create the PingUrlSync task, it gets added to the queue and is waiting to be scheduled.\nWhen calling \u003ccode\u003eGetAwaiter().GetResult()\u003c/code\u003e, we are asking the runtime to wait on the current thread for the task to complete.\nEssentially, for every \u003ccode\u003ePingUrlSync\u003c/code\u003e call, we now have at least 1 new task and 1 blocked thread.\nWhen we call this method multiple times concurrently, we have multiple blocked threads.\u003c/p\u003e\n\u003cp\u003eThe CLR thread pool is not an unlimited resource. At some point, if you run enough concurrent tasks, you\u0026rsquo;ll end up blocking all the threads, and tasks in the queue will not have any threads available to run on.\u003c/p\u003e\n\u003cp\u003eAt this point, the blocked threads are waiting for tasks to complete, and tasks in the queue are waiting for a thread to free up so they can do their work.\nWe now have a deadlock, and it results in a hung state.\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode class=\"language-mermaid\" data-lang=\"mermaid\"\u003egraph TD\n    subgraph \u0026#34;Task Queue\u0026#34;\n        T1[Task 1]\n        T2[Task 2]\n        T3[Task 3]\n        T4[Task 4]\n        T5[Task 5]\n    end\n    \n    subgraph \u0026#34;Thread Pool (3 threads)\u0026#34;\n        TP1[Thread 1] --\u0026gt; |\u0026#34;Blocked waiting for Task 1\u0026#34;| B1[Blocked]\n        TP2[Thread 2] --\u0026gt; |\u0026#34;Blocked waiting for Task 2\u0026#34;| B2[Blocked]\n        TP3[Thread 3] --\u0026gt; |\u0026#34;Blocked waiting for Task 3\u0026#34;| B3[Blocked]\n    end\n    \n    T1 --\u0026gt; TP1\n    T2 --\u0026gt; TP2\n    T3 --\u0026gt; TP3\n    \n    style T1 fill:#f9f,stroke:#333,stroke-width:2px\n    style T2 fill:#f9f,stroke:#333,stroke-width:2px\n    style T3 fill:#f9f,stroke:#333,stroke-width:2px\n    style T4 fill:#f9f,stroke:#333,stroke-width:2px\n    style T5 fill:#f9f,stroke:#333,stroke-width:2px\n    style TP1 fill:#bbf,stroke:#333,stroke-width:2px\n    style TP2 fill:#bbf,stroke:#333,stroke-width:2px\n    style TP3 fill:#bbf,stroke:#333,stroke-width:2px\n    style B1 fill:#f66,stroke:#333,stroke-width:2px\n    style B2 fill:#f66,stroke:#333,stroke-width:2px\n    style B3 fill:#f66,stroke:#333,stroke-width:2px\n\u003c/code\u003e\u003c/pre\u003e","date_modified":"2026-04-11T09:06:24Z","date_published":"2025-03-20T22:12:03Z","id":"https://mayureshwaykole.com/posts/get-awaiter/","image":"https://mayureshwaykole.com/images/social-preview.png","summary":"Deep dive into thread pool exhaustion caused by GetAwaiter().GetResult() in C#. Learn why blocking on async code leads to deadlocks and how to fix it.","tags":["C#","Async/Await","Concurrency","Thread Pool","Thread Starvation"],"title":"Thread Exhaustion Due to GetAwaiter().GetResult() in C#","url":"https://mayureshwaykole.com/posts/get-awaiter/"}],"language":"en","title":"Thread Starvation | Mayuresh Waykole","version":"https://jsonfeed.org/version/1.1"}