【问题标题】:Is there a way to get the stacktraces for all threads in c#, like java.lang.Thread.getAllStackTraces()?有没有办法获取 c# 中所有线程的堆栈跟踪,例如 java.lang.Thread.getAllStackTraces()?
【发布时间】:2010-01-13 15:25:39
【问题描述】:

在 java 中,可以获得所有正在运行的线程的堆栈跟踪的快照。 这是通过java.lang.Thread.getAllStackTraces() 完成的(它返回Map<Thread,StackTraceElement[]>)。

.net 如何做到这一点?

【问题讨论】:

    标签: c# .net multithreading


    【解决方案1】:

    所以我实际上只需要弄清楚如何做到这一点——还没有在生产中广泛使用这个解决方案,但是有一个相对较新的库,叫做 ClrMd。

    http://blogs.msdn.com/b/dougste/archive/2013/05/04/clrmd-net-crash-dump-and-live-process-inspection.aspx

    使用它,我可以附加到我自己的进程并获取所有活动线程的堆栈跟踪。在重新启动我们的应用程序之前检测到死锁时使用它:

    var result = new Dictionary<int, string[]>();
    
    var pid = Process.GetCurrentProcess().Id;
    
    using (var dataTarget = DataTarget.AttachToProcess(pid, 5000, AttachFlag.Passive))
    {
        ClrInfo runtimeInfo = dataTarget.ClrVersions[0];
        var runtime = runtimeInfo.CreateRuntime();
    
        foreach (var t in runtime.Threads)
        {
            result.Add(
                t.ManagedThreadId,
                t.StackTrace.Select(f =>
                {
                    if (f.Method != null)
                    {
                        return f.Method.Type.Name + "." + f.Method.Name;
                    }
    
                    return null;
                }).ToArray()
            );
        }
    }
    
    var json = JsonConvert.SerializeObject(result);
    
    zip.AddEntry("_threads.json", json);
    

    要让它在同一个过程中发挥作用,真正重要的是AttachFlag.Passive

    如果你只是做DataTarget.AttachToProcess(pid, 5000),它会做一个“侵入式”附加,试图暂停这个过程。当您尝试附加到您自己的进程时,这会引发异常,我假设是因为您在尝试从您的应用程序附加或类似的东西时无法暂停您的应用程序。

    【讨论】:

    • 刚刚从客户端位置取回了一些调试转储,这对我们有用。很酷。
    • 很棒的代码,谢谢!我可以建议像 f.Method.GetFullSignature() 一样添加方法签名。在覆盖的情况下可能会有所帮助
    • 这是因为 ClrMD 目前不支持 .NET 4.6。而这个帖子有一个解决方案:stackoverflow.com/questions/31633541/…
    • An unhandled exception of type 'System.NullReferenceException' occurred in Microsoft.Diagnostics.Runtime.dll in result.Add
    • 注意:这个库在读取堆栈跟踪时会导致内存泄漏:github.com/Microsoft/clrmd/issues/47。您可能不想在生产中使用它。
    【解决方案2】:

    如果您想获取托管代码中所有线程的堆栈跟踪,那么您可以尝试mdbg。看看Managed Stack Explorer 它确实使用 mdbg 并获取所有线程的堆栈。

    【讨论】:

      【解决方案3】:

      如果您只想将其用于调试目的,WinDbg 的 SOS 扩展可以为您提供此信息。

      要运行的命令是“*~e !clrstack”。

      在一个正在运行的 C# 程序中,没有公开方式来枚举托管线程或通过 ID 查找它们。即使可以,在不同线程上获取堆栈跟踪也可能需要将其挂起,这会带来一些副作用风险(请参阅为什么 this is obsolete)。

      另一种选择是获取已知的线程,并在闲暇时对其进行扫描。这可能只有在您显式创建线程对象而不是使用线程池时才有可能。

      也就是说,我也很难看出这种方法的目的是什么。如果是为了调试,还有更强大的技术可以在内存中或小型转储上完成。如果是用于日志记录,那么让日志记录调用贡献自己的堆栈可能是有意义的。

      【讨论】:

        【解决方案4】:

        更新了代码,以获取使用来自@Joshua Evensen 的答案作为基础的所有堆栈跟踪的快照。你仍然需要安装 NuGet 包CLR Memory Diagnostics (ClrMD)。这个 sn-p 还包含额外的代码来获取线程名称,但如果您只需要堆栈跟踪,则不需要。

        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();
                }
            }
        }
        

        【讨论】:

          【解决方案5】:

          您可以使用ProcInsp,它有一个 Web API,可以获取带有 JSON 堆栈的线程。 Web API 可在/Process/%PID%/Threads 获得(使用GET 请求)。

          免责声明:我是 ProcInsp 的开发人员。该工具受 MIT 许可,可免费使用。

          【讨论】:

            【解决方案6】:

            正如 Mason of Words 所暗示的,从托管代码本身来看,这似乎是不可能的。 您能否解释一下为什么需要这个:可能有更好的解决方案?

            例如,如果您在 Visual Studio 中附加到进程并按“暂停”,则“线程”窗口将列出所有托管线程,“堆栈跟踪”窗口可以显示每个线程的当前堆栈跟踪。这样就够了吗?

            【讨论】:

              【解决方案7】:

              有一个 StackTrace 类

              var trace = new System.Diagnostics.StackTrace(exception);

              http://msdn.microsoft.com/en-us/library/system.diagnostics.stacktrace.aspx

              【讨论】:

                【解决方案8】:

                您可以在 System.Diagnostics.Process.GetCurrentProcess().Threads 上循环,并为每个 Thread 创建一个 StackTrace 对象,其 .ctor 将 Thread 作为其参数。

                【讨论】:

                • 与@MarcosMeli 的回答一样,这是不正确的。正如@Hans Loken 所说,GetCurrentProcess() 方法返回一个 ProcessThreadCollection,其中包含 ProcessThread 对象,而不是 Thread 对象。据我所知,您无法从 ProcessThread 获取堆栈跟踪,因为它代表 Windows 本机线程。
                猜你喜欢
                • 2012-07-07
                • 2012-06-18
                • 1970-01-01
                • 1970-01-01
                • 2017-06-13
                • 1970-01-01
                • 1970-01-01
                • 2021-05-13
                • 1970-01-01
                相关资源
                最近更新 更多