50

I want to list all running threads but not by using the List<> class. I want to dynamically observe running threads. How can I do that?

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
sanchop22
  • 2,729
  • 12
  • 43
  • 66
  • 5
    will `System.Diagnostics.Process.GetCurrentProcess().Threads` suffice? – Marc Gravell Apr 25 '12 at 12:44
  • 5
    possible duplicate of [How can I enumerate all managed threads in C#?](http://stackoverflow.com/questions/466799/how-can-i-enumerate-all-managed-threads-in-c) – Steve Apr 25 '12 at 12:48

3 Answers3

72

Method 1: Get OS Threads

This gets the list of OS threads:

ProcessThreadCollection currentThreads = Process.GetCurrentProcess().Threads;

foreach (ProcessThread thread in currentThreads)
{
}

Method 2: Get Managed Threads

Managed threads sit on top of OS threads. The IDs are different, and in theory, more than one Managed Thread may sit on top of a single OS thread (although I havn't actually observed this).

It turns out that getting managed threads is trickier than it really should be.

Method 2.1: Simplest code to get Managed Threads

  1. Check out Microsoft.Diagnostics.Runtime on GitHub.
  2. Install NuGet package CLR Memory Diagnostics (ClrMD).

You can then use said NuGet package to attach to your own process, and read the managed threads out:

using Microsoft.Diagnostics.Runtime;

using (DataTarget target = DataTarget.AttachToProcess(
    Process.GetCurrentProcess().Id, 5000, AttachFlag.Passive))
{
    ClrRuntime runtime = target.ClrVersions.First().CreateRuntime();
    foreach (ClrThread thread in runtime.Threads)
    {
    }
}

Method 2.2: Example of how to search through managed threads by stack trace

Unfortunately, I couldn't find any way to search through the list of threads by the thread name.

However, all is not lost: here is an example of how to create a managed thread, then find it by searching through the stack frames for a match on the namespace, then print out its properties:

namespace MyTest
{
    int managedThreadId = 0;
    var task = Task.Run(
        () =>
        {
            // Unfortunately, cant see "Testing" anywhere in result returned
            // from NuGet package ClrMD ...
            Thread.CurrentThread.Name = "Testing";
            Thread.Sleep(TimeSpan.FromDays(1));
        });


    // ... so we look for our thread by the first word in this namespace.
    string startOfThisNamespace = this.GetType().Namespace.ToString().Split('.')[0]; // Is "MyTest".
    using (DataTarget target = DataTarget.AttachToProcess(Process.GetCurrentProcess().Id, 5000, AttachFlag.Passive))
    {
        ClrRuntime runtime = target.ClrVersions.First().CreateRuntime();

        foreach (ClrThread thread in runtime.Threads)
        {
            IList<ClrStackFrame> stackFrames = thread.StackTrace;

            List<ClrStackFrame> stackframesRelatedToUs = stackFrames
                .Where(o => o.Method != null && o.Method.ToString().StartsWith(startOfThisNamespace)).ToList();

            if (stackframesRelatedToUs.Count > 0)
            {
                Console.Write("ManagedThreadId: {0}, OSThreadId: {1}, Thread: IsAlive: {2}, IsBackground: {3}:\n", thread.ManagedThreadId, thread.OSThreadId, thread.IsAlive, thread.IsBackground);
                Console.Write("- Stack frames related namespace '{0}':\n", startOfThisNamespace);
                foreach (var s in stackframesRelatedToUs)
                {
                    Console.Write("  - StackFrame: {0}\n", s.Method.ToString());
                }
            }
        }
    }
}

You can also find the correct match by saving ManagedThreadId within the thread that you create, then looking for this same ID in runtime.Threads.

Testing

Tested with all combinations of:

  • Visual Studio 2015 SP1
  • .NET 4.5
  • .NET 4.6.0
  • .NET 4.6.1
  • C# 5.0
  • C# 6.0

References

See ClrMd throws exception when creating runtime.

Community
  • 1
  • 1
Contango
  • 76,540
  • 58
  • 260
  • 305
  • The best answer I am looking for. Thanks. – Levesque Xylia Apr 02 '21 at 02:24
  • `Could not load file or assembly 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)`. Do I really need to install extra software just in order to see the threads in my application? – Dominique Jul 12 '23 at 06:41
  • @Dominique Did you install the NuGet package that was mentioned in the answer? If you want to query managed threads, then this is required. Are you are aware of a dependency-free method to achieve this? – Contango Jul 15 '23 at 22:03
60
using System.Diagnostics;

ProcessThreadCollection currentThreads = Process.GetCurrentProcess().Threads;

foreach (ProcessThread thread in currentThreads)    
{
   // Do whatever you need
}
İlker Elçora
  • 610
  • 5
  • 13
Xaqron
  • 29,931
  • 42
  • 140
  • 205
3

Updated code to get a snapshot of all stack traces that uses the answer from @Contango as a base. You'll still need to install NuGet package CLR Memory Diagnostics (ClrMD). This snippet also includes extra code to get the thread names, but this isn't required if you just want the stack traces.

edit: As pointed out by @abulhol in the commends, the fields m_ManagedThreadId and m_Name may be _managedThreadId and _name depending on your C# version.

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.Diagnostics.Runtime;

namespace CSharpUtils.wrc.utils.debugging
{
    public static class StackTraceAnalysis
    {
        public static string GetAllStackTraces()
        {
            var result = new StringBuilder();
            
            using (var target = DataTarget.CreateSnapshotAndAttach(Process.GetCurrentProcess().Id))
            {
                var runtime = target.ClrVersions.First().CreateRuntime();

                // We can't get the thread name from the ClrThead objects, so we'll look for
                // Thread instances on the heap and get the names from those.    
                var threadNameLookup = new Dictionary<int, string>();
                foreach (var obj in runtime.Heap.EnumerateObjects())
                {
                    if (!(obj.Type is null) && obj.Type.Name == "System.Threading.Thread")
                    {
                        var threadId = obj.ReadField<int>("m_ManagedThreadId");
                        var threadName = obj.ReadStringField("m_Name");
                        threadNameLookup[threadId] = threadName;
                    }
                }

                foreach (var thread in runtime.Threads)
                {
                    threadNameLookup.TryGetValue(thread.ManagedThreadId, out string threadName);
                    result.AppendLine(
                        $"ManagedThreadId: {thread.ManagedThreadId}, Name: {threadName}, OSThreadId: {thread.OSThreadId}, Thread: IsAlive: {thread.IsAlive}, IsBackground: {thread.IsBackground}");
                    foreach (var clrStackFrame in thread.EnumerateStackTrace())
                        result.AppendLine($"{clrStackFrame.Method}");
                }
            }

            return result.ToString();
        }
    }
}
Will Calderwood
  • 4,393
  • 3
  • 39
  • 64
  • thanks for this code, it basically works. But I cannot get the thread IDs, I get this exception in the first `foreach` loop: `System.ArgumentException: Type 'System.Threading.Thread' does not contain a field named 'm_ManagedThreadId' at Microsoft.Diagnostics.Runtime.ClrObject.ReadField[T](String fieldName)` – abulhol Apr 21 '23 at 14:08
  • @abulhol My only thought is the field might have been renamed in a different version of C#. Try going through all the fields in obj.Type.Fields and see if there's anything else that looks like it might be correct. – Will Calderwood Apr 22 '23 at 17:25
  • 1
    I have found out, the field names changed to `_managedThreadId` and `_name`, see https://source.dot.net/#System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs,07a0689f2914aaaa. This is with .NET 6.0 btw. – abulhol Apr 24 '23 at 06:25