chrisnas/debuggingextensions/dotnet-threads-analysis

Claude Code and agent skill by chrisnas/debuggingextensions

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Analyze .NET application threading issues using CLI diagnostic tools. Use when investigating thread contention, deadlocks, thread pool starvation, sync-over-async patterns, or frozen/unresponsive applications — on a live process or from a memory dump. Works with dotnet-counters, dotnet-trace, dotnet-dump, and dotnet-pstacks.

Install

npx vskill@latest install chrisnas/debuggingextensions/dotnet-threads-analysis

SKILL.md

.NET Thread Analysis

Diagnose thread contention, deadlocks, thread pool starvation, and sync-over-async issues in .NET applications using the standard Microsoft diagnostic CLI tools and custom ClrMD-based tools — no code changes to the target application required.

Prerequisites

The following tools must be installed as global .NET CLI tools:

bash
dotnet tool install -g dotnet-dump
dotnet tool install -g dotnet-counters
dotnet tool install -g dotnet-trace
dotnet tool install -g dotnet-pstacks

Verify with dotnet tool list -g. A .NET runtime (6.0+) is required on the machine; an SDK is not needed to run the tools.

Key Technique: Non-Interactive SOS Commands

dotnet-dump analyze is interactive by default, which does not work well with agents. Always use the -c flag to run SOS commands non-interactively:

bash
dotnet-dump analyze <dump-path> -c "<command1>" -c "<command2>" -c "exit"

Multiple -c flags are executed in sequence. Always end with -c "exit".


Note Always start with threads and dotnet-pstacks for a quick overview of thread state distribution before diving into individual call stacks. On Windows, if WinDbg is installed, use !locks and !runaway for additional native lock and CPU-time-per-thread details.

Live Process Investigation

Use this workflow when the application is still running and you want to observe threading behavior or detect contention/starvation without taking a full dump.

Identify the Target
bash
dotnet-dump ps

Lists running .NET processes with PID and name. Confirm the target PID.

Monitor Thread Counters
bash
dotnet-counters monitor -p <PID> --counters System.Runtime

Key counters to watch:

  • threadpool-thread-count — climbing steadily = possible starvation or

sync-over-async pattern

  • threadpool-queue-length — high values = work items waiting for threads
  • monitor-lock-contention-count — spikes = lock contention
  • threadpool-completed-items-count — compare with queue length to gauge

throughput

Watch counters for at least 30 seconds to distinguish a genuine trend from a transient spike. If threadpool-thread-count is steadily climbing (not just spiking under load), this is a strong signal of sync-over-async or thread pool starvation. Compare threadpool-queue-length against threadpool-completed-items-count over time: a growing queue with flat completions confirms the pool cannot keep up.

Press q to stop.

Collect an EventPipe Trace
bash
dotnet-trace collect -p <PID> --providers Microsoft-Windows-DotNETRuntime

Produces a .nettrace file for offline analysis. Add Microsoft-DotNETCore-SampleProfiler for CPU profiling to identify hot threads.

Escalate to Full Dump

If counters reveal contention or starvation but you need to see exact call stacks and lock ownership, capture a full dump:

bash
dotnet-dump collect -p <PID>

Warning: This briefly freezes the process. Confirm with the user before running on a production system. Then continue with the Dump-Based Investigation workflow below.


Dump-Based Investigation

Use this workflow when analyzing a .dmp file — either provided by the user or captured via dotnet-dump collect.

Parallel Stacks Overview
bash
dotnet-pstacks <dump>

Merges threads with identical call stacks. Quickly reveals:

  • Many threads blocked at the same lock frame -> contention
  • Two groups stuck in opposite lock acquisition -> potential deadlock
List All Managed Threads
bash
dotnet-dump analyze <dump> -c "threads" -c "exit"

Shows thread ID, OS ID, state, lock count, and exception info.

Full Stack Trace for All Threads
bash
dotnet-dump analyze <dump> -c "clrstack -all" -c "exit"

Look for Monitor.Enter, Monitor.Wait, SemaphoreSlim.Wait, or ManualResetEventSlim.Wait frames to identify where threads are blocked.

Sync Block Table (Lock Ownership)
bash
dotnet-dump analyze <dump> -c "syncblk" -c "exit"

Shows which thread owns each monitor lock. Columns:

  • MonitorHeld: lock held count
  • Owning Thread: the thread ID that holds the lock
  • SyncBlock Owner: the object being locked on
Deadlock Detection

When the application is frozen and not responding:

  1. Run syncblk to find which threads own locks
  2. Run clrstack -all to see where each thread is blocked
  3. Cross-reference: if Thread A owns Lock 1 and is blocked waiting for Lock 2,

while Thread B owns Lock 2 and is blocked waiting for Lock 1 -> deadlock

  1. Run dotnet-pstacks for visual confirmation of the circular wait pattern

Quick deadlock check sequence:

bash
dotnet-dump analyze <dump> -c "syncblk" -c "threads" -c "clrstack -all" -c "exit"

Then run separately:

bash
dotnet-pstacks <dump>

Report the deadlock cycle clearly: "Thread X holds Lock A (object 0x...) and waits for Lock B (object 0x...); Thread Y holds Lock B and waits for Lock A."

Inspect Contended Code

When a culprit is found (contended lock, deadlock participant, or sync-over-async blocker), inspect the lock object and surrounding code to understand the root cause:

bash
dotnet-dump analyze <dump> -c "dumpobj <lock-object-address>" -c "exit"

Use dumpmt, dumpclass, and dumpil to examine the type that owns or acquires the lock. Another strategy is to dump the assembly from the dump with .writemem on the corresponding module and decompile the class methods with ilspycmd (to install with dotnet tool install ilspycmd -g if needed) to review the actual lock acquisition order, async call chains, or missing ConfigureAwait(false) calls.


Diagnosis Patterns

SymptomLikely CauseKey Command
Many threads at same lock frameLock contentiondotnet-pstacks + syncblk
App frozen, threads waiting on locksDeadlock (circular dependency)syncblk + clrstack -all + dotnet-pstacks
Thread pool count climbing (live)Sync-over-async / starvationdotnet-counters
High queue length, low completion rateThread pool exhaustiondotnet-counters
Threads blocked on SemaphoreSlim.WaitAsync throttle saturationclrstack -all + dotnet-pstacks
Many threads blocked at Task.Result / .GetAwaiter().GetResult()Sync-over-asyncclrstack -all + dotnet-pstacks

Investigation Summary — summary markdown file

Throughout the investigation, maintain a summary file with the following format <date>-<time>_thread_analysis_SUMMARY.md file in the current working directory. Create it before the first command and update it after every step. Use the following structure:

markdown
# Thread Investigation Summary
**Date:** YYYY-MM-DD
**Target:** <process name / dump file path>
**Symptom:** <initial problem description>
## Investigation Steps
### Step N — <brief description>
**Command:**
\```
<exact command line>
\```
**Result:**
<relevant output excerpt — thread counts, parallel stacks groups, sync block
owners, deadlock cycles, counter snapshots, etc.>
**Interpretation:**
<what the result means for the investigation>
**Next action:**
<what will be done next and why>
<!-- repeat for each step -->
## Commands Used
| # | Command | Purpose |
|---|---------|---------|
| 1 | `dotnet-dump ps` | Identify target process |
| 2 | `dotnet-pstacks dump.dmp` | Parallel stacks overview |
| ... | ... | ... |
## Conclusion
**Root cause:** <identified cause or remaining candidates>
**Evidence chain:** <which steps and results led to the diagnosis>
**Recommended remediation:** <concrete fix suggestions>

The file serves as a full audit trail the user can review, share, or archive.

Safety Guardrails

  • Don't forget to generate the summary file
  • Never kill a process without explicit user consent
  • Warn before `dotnet-dump collect` on production — it freezes the process
  • Prefer live counters over full dump for initial investigation
  • Do not attach to system-critical processes (PID 0, PID 4, services)
  • If unsure about a process identity, run dotnet-dump ps and confirm with the

user before proceeding

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
230
Repository forks
21
7-day trend score
0

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/chrisnas/debuggingextensions/dotnet-threads-analysis/badge)](https://verified-skill.com/skills/chrisnas/debuggingextensions/dotnet-threads-analysis)

Something wrong with this skill or its page? Tell us.