{"id":257253,"date":"2026-09-22T10:05:25","date_gmt":"2026-09-22T17:05:25","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/visualstudio\/?p=257253"},"modified":"2026-09-21T16:43:53","modified_gmt":"2026-09-21T23:43:53","slug":"today-i-will-debug-a-production-crash","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/visualstudio\/today-i-will-debug-a-production-crash\/","title":{"rendered":"Today I will&#8230; debug a production crash"},"content":{"rendered":"<p>We&#8217;ve all experienced this, the desktop application is hanging, the window is greyed out, and we&#8217;re just waiting for it to start to respond. Or we&#8217;re on a website and are sure that it was going to load faster than this, but we&#8217;re just waiting the loading icon spin around and around. These sorts of problems, when they happen in production, can be difficult to diagnose since you often need to replicate them, from machine specifications to traffic loads.<\/p>\n<p>Today we&#8217;re going to look at one of the possible problems, having too many active threads running in a C# application, and how we can debug the crash after the fact using a memory dump.<\/p>\n<h2>Capturing a Memory Dump<\/h2>\n<p>Before we can start debugging the production application&#8217;s memory dump, we&#8217;re going to need to capture that. We&#8217;ll be using a C# application for this, but we won&#8217;t dive too deeply into the creation of the Memory Dump here, for that, check out the <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/creating-a-memory-dump-in-csharp\/\">companion blog on the .NET Blog<\/a>. The short version of what we&#8217;re going to do is:<\/p>\n<ol>\n<li>Monitor the <code>ThreadPool<\/code> for when it&#8217;s taking longer than a particular time to complete a thread.<\/li>\n<li>Dump the memory to a Memory Dump file that we can open in Visual Studio.<\/li>\n<\/ol>\n<h3>Monitoring the <code>ThreadPool<\/code><\/h3>\n<p>To monitor the <code>ThreadPool<\/code> we&#8217;re going to create a new <code>Thread<\/code> that we periodically put into the <code>ThreadPool<\/code> and observe how long it takes to complete. If it takes longer than a timeout that we deem ideal (we&#8217;re going to use three seconds for this sample), we&#8217;ll dump the state of the <code>ThreadPool<\/code> for diagnostics.<\/p>\n<p>Here&#8217;s our basic monitor code:<\/p>\n<pre><code class=\"csharp\">var interval = 3_000;\nvar thread = new Thread(() =&gt;\n{\n    while (true)\n    {\n        Thread.Sleep(interval);\n\n        Stopwatch stopwatch = Stopwatch.StartNew();\n\n        Task.Run(() =&gt;\n        {\n            stopwatch.Stop();\n        }).Wait();\n\n        if (stopwatch.ElapsedMilliseconds &gt; interval)\n        {\n            \/\/ Took over the interval to complete\n            Console.WriteLine($\"Task took too long: {stopwatch.ElapsedMilliseconds} ms\");\n\n            string path = Path.Combine(AppContext.BaseDirectory, $\"fulldump-{Environment.ProcessId}-{DateTime.Now:yyyyMMdd-HHmmss}.dmp\");\n\n            MiniDumper.WriteCurrentProcess(path);\n        }\n    }\n})\n{\n    Name = \"ThreadPool Watcher\",\n    IsBackground = true\n};\nthread.Start();\n<\/code><\/pre>\n<p>As I mentioned, we have a <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/creating-a-memory-dump-in-csharp\/\">companion post<\/a> that will go into more details on this code, but what is doing is creating a new <code>Thread<\/code> that, every three seconds, creates a new <code>Stopwatch<\/code>, then puts a <code>Task<\/code> onto the <code>ThreadPool<\/code> that will stop the stopwatch. Once that <code>Task<\/code> completes, we check how long it took, and if it took longer than our threshold, we&#8217;ll call some Windows APIs to create our dump.<\/p>\n<h3>Creating a Crash<\/h3>\n<p>For this demo, we&#8217;re going to exhaust the <code>ThreadPool<\/code> by spinning up a lot of async tasks that are going to take a long time to complete.<\/p>\n<pre><code class=\"csharp\">Parallel.For(0, 100, (i) =&gt; {\n    Console.WriteLine(\"Running task {0}\", i);\n    Thread.Sleep(10_000);\n});\n<\/code><\/pre>\n<p>When our application runs, it&#8217;s going to start spinning up background threads, they&#8217;ll &#8220;do work&#8221; (ie, sleep for ten seconds), but since they haven&#8217;t finished in a timely fashion more tasks will be added, until we end up with our application hanging because the <code>ThreadPool<\/code> is overloaded. Thankfully, our memory dump will be captured for us to debug.<\/p>\n<h2>Opening a Memory Dump<\/h2>\n<p>Our application has created a <code>.dmp<\/code> file that is the memory dump, which we can open in Visual Studio.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/memory-dump-overview.webp\"><img decoding=\"async\" width=\"1935\" height=\"1094\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/memory-dump-overview.webp\" alt=\"Memory Dump loaded in Visual Studio\" class=\"alignnone wp-image-257255\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/memory-dump-overview.webp 1935w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/memory-dump-overview-300x170.webp 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/memory-dump-overview-1024x579.webp 1024w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/memory-dump-overview-768x434.webp 768w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/memory-dump-overview-1536x868.webp 1536w\" sizes=\"(max-width: 1935px) 100vw, 1935px\" \/><\/a><\/p>\n<p>There&#8217;s a lot of things we can do now that we&#8217;ve opened the memory dump, but because we&#8217;re trying to get to the bottom of why our application crashed, we&#8217;re going to debug this memory dump using the <strong>Debug with Mixed<\/strong>, which will launch a debugging session which can debug both managed (C#) and native code.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/application-paused.webp\"><img decoding=\"async\" width=\"1935\" height=\"1094\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/application-paused.webp\" alt=\"Application Paused in Visual Studio\" class=\"alignnone wp-image-257256\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/application-paused.webp 1935w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/application-paused-300x170.webp 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/application-paused-1024x579.webp 1024w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/application-paused-768x434.webp 768w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/application-paused-1536x868.webp 1536w\" sizes=\"(max-width: 1935px) 100vw, 1935px\" \/><\/a><\/p>\n<p>This is a very similar experience to if we had put a breakpoint in place and run the application from Visual Studio with the debugger attached. We can see the <strong>Call Stack<\/strong>, we have our <strong>Autos<\/strong> showing the value of local variables (<code>i = 2<\/code>), we can navigate around the decompiled file, but what it&#8217;s not showing us is <em>why<\/em> the <code>ThreadPool<\/code> was having problems at this point in time. For that, we&#8217;re going to use another view in the debugger.<\/p>\n<h2>Viewing Parallel Stacks<\/h2>\n<p>From the menu <strong>Debug<\/strong> -> <strong>Windows<\/strong> we&#8217;re going to select <strong>Parallel Stacks<\/strong> (CTRL + SHIFT + D, S) and this will load up a new window that gives us an overview of all the threads that are running in parallel of within our application.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/parallel-stacks-view.webp\"><img decoding=\"async\" width=\"1935\" height=\"1094\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/parallel-stacks-view.webp\" alt=\"Parallel Stacks Window\" class=\"alignnone size-medium wp-image-257257\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/parallel-stacks-view.webp 1935w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/parallel-stacks-view-300x170.webp 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/parallel-stacks-view-1024x579.webp 1024w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/parallel-stacks-view-768x434.webp 768w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/parallel-stacks-view-1536x868.webp 1536w\" sizes=\"(max-width: 1935px) 100vw, 1935px\" \/><\/a><\/p>\n<p>From here we can see three threads, there&#8217;s <strong>Main Thread<\/strong>, which is the application running, there&#8217;s <strong>ThreadPool Watcher<\/strong>, which is the thread we defined earlier, but then there&#8217;s a box in the middle that shows <em>27 Threads<\/em> at the top &#8211; and this is where our problem can be spotted. There are 27 threads all blocked at the same place (which happens to be our <code>Thread.Sleep(10_000)<\/code>), which is well above the number of threads I should be running on my 16-core CPU. If we were to hover over the method shown for that collection of threads, we&#8217;ll see where each of them is paused at.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/viewing-thread-list.webp\"><img decoding=\"async\" width=\"745\" height=\"478\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/viewing-thread-list.webp\" alt=\"Viewing Thread List\" class=\"alignnone wp-image-257258\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/viewing-thread-list.webp 745w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/viewing-thread-list-300x192.webp 300w\" sizes=\"(max-width: 745px) 100vw, 745px\" \/><\/a><\/p>\n<p>From here, we can click into any of the threads, view their unique state at that point of time in the application, and isolate where the problem is coming from.<\/p>\n<h2>Analyzing Parallel Stacks with Copilot<\/h2>\n<p>Understanding the root cause of a problem across an application that is using a lot of threads can be challenging, and while the memory dump contains a lot of information, it can be difficult to try and find that root cause. To help sift through all the information, we can level the GitHub Copilot integration with the Parallel Stacks window. On the Parallel Stacks window there is a Copilot icon, and that will start a new chat session with the context being the current view and Copilot having access to the memory dump. Copilot can then analyze the threads, their state, and their stacks to help come up with a plan for resolving the problem.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/copilot-analysis-parallel-stacks.webp\"><img decoding=\"async\" width=\"359\" height=\"794\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/copilot-analysis-parallel-stacks.webp\" alt=\"copilot analysis parallel stacks image\" class=\"alignnone wp-image-257259\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/copilot-analysis-parallel-stacks.webp 359w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/09\/copilot-analysis-parallel-stacks-136x300.webp 136w\" sizes=\"(max-width: 359px) 100vw, 359px\" \/><\/a><\/p>\n<p>From this analysis Copilot has concluded that this wasn&#8217;t a crash, but there does appear to be a <strong>thread-pool saturation \/ blocking workload<\/strong>.<\/p>\n<h2>Wrapping Up<\/h2>\n<p>Being able to diagnose an application crash that has happened on a production system can be a difficult undertaking because we don&#8217;t have the application state on hand. Throughout this post we&#8217;ve seen how we can generate a memory dump and then load that into Visual Studio to debug that session and view the threads that were active at the time our application became unresponsive.<\/p>\n<p>Through the memory dump we could inspect the variables and call stack that led to our problem and then combining that with the Parallel Stacks window we were able to visualise our thread usage and find the thread saturation that was happening within the application. Lastly, we combine this with GitHub Copilot to have some AI assisted debugging, giving additional insights into what&#8217;s happened within our application.<\/p>\n<p>To learn more about the code that was used to capture the memory dump, refer to the <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/creating-a-memory-dump-in-csharp\/\">companion blog post<\/a> on the .NET blog.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We&#8217;ve all experienced this, the desktop application is hanging, the window is greyed out, and we&#8217;re just waiting for it to start to respond. Or we&#8217;re on a website and are sure that it was going to load faster than this, but we&#8217;re just waiting the loading icon spin around and around. These sorts of [&hellip;]<\/p>\n","protected":false},"author":40333,"featured_media":257265,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[6962,6963,155],"tags":[6398,1665,6427],"class_list":["post-257253","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-debug","category-productivity","category-visual-studio","tag-async","tag-debugging","tag-parallel-stacks"],"acf":[],"blog_post_summary":"<p>We&#8217;ve all experienced this, the desktop application is hanging, the window is greyed out, and we&#8217;re just waiting for it to start to respond. Or we&#8217;re on a website and are sure that it was going to load faster than this, but we&#8217;re just waiting the loading icon spin around and around. These sorts of [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts\/257253","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/users\/40333"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/comments?post=257253"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts\/257253\/revisions"}],"predecessor-version":[{"id":257348,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts\/257253\/revisions\/257348"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/media\/257265"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/media?parent=257253"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/categories?post=257253"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/tags?post=257253"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}