【发布时间】:2010-01-13 15:25:39
【问题描述】:
在 java 中,可以获得所有正在运行的线程的堆栈跟踪的快照。
这是通过java.lang.Thread.getAllStackTraces() 完成的(它返回Map<Thread,StackTraceElement[]>)。
.net 如何做到这一点?
【问题讨论】:
标签: c# .net multithreading
在 java 中,可以获得所有正在运行的线程的堆栈跟踪的快照。
这是通过java.lang.Thread.getAllStackTraces() 完成的(它返回Map<Thread,StackTraceElement[]>)。
.net 如何做到这一点?
【问题讨论】:
标签: c# .net multithreading
所以我实际上只需要弄清楚如何做到这一点——还没有在生产中广泛使用这个解决方案,但是有一个相对较新的库,叫做 ClrMd。
使用它,我可以附加到我自己的进程并获取所有活动线程的堆栈跟踪。在重新启动我们的应用程序之前检测到死锁时使用它:
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),它会做一个“侵入式”附加,试图暂停这个过程。当您尝试附加到您自己的进程时,这会引发异常,我假设是因为您在尝试从您的应用程序附加或类似的东西时无法暂停您的应用程序。
【讨论】:
An unhandled exception of type 'System.NullReferenceException' occurred in Microsoft.Diagnostics.Runtime.dll in result.Add
如果您想获取托管代码中所有线程的堆栈跟踪,那么您可以尝试mdbg。看看Managed Stack Explorer 它确实使用 mdbg 并获取所有线程的堆栈。
【讨论】:
如果您只想将其用于调试目的,WinDbg 的 SOS 扩展可以为您提供此信息。
要运行的命令是“*~e !clrstack”。
在一个正在运行的 C# 程序中,没有公开方式来枚举托管线程或通过 ID 查找它们。即使可以,在不同线程上获取堆栈跟踪也可能需要将其挂起,这会带来一些副作用风险(请参阅为什么 this is obsolete)。
另一种选择是获取已知的线程,并在闲暇时对其进行扫描。这可能只有在您显式创建线程对象而不是使用线程池时才有可能。
也就是说,我也很难看出这种方法的目的是什么。如果是为了调试,还有更强大的技术可以在内存中或小型转储上完成。如果是用于日志记录,那么让日志记录调用贡献自己的堆栈可能是有意义的。
【讨论】:
更新了代码,以获取使用来自@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();
}
}
}
【讨论】:
您可以使用ProcInsp,它有一个 Web API,可以获取带有 JSON 堆栈的线程。 Web API 可在/Process/%PID%/Threads 获得(使用GET 请求)。
免责声明:我是 ProcInsp 的开发人员。该工具受 MIT 许可,可免费使用。
【讨论】:
正如 Mason of Words 所暗示的,从托管代码本身来看,这似乎是不可能的。 您能否解释一下为什么需要这个:可能有更好的解决方案?
例如,如果您在 Visual Studio 中附加到进程并按“暂停”,则“线程”窗口将列出所有托管线程,“堆栈跟踪”窗口可以显示每个线程的当前堆栈跟踪。这样就够了吗?
【讨论】:
有一个 StackTrace 类
var trace = new System.Diagnostics.StackTrace(exception);
http://msdn.microsoft.com/en-us/library/system.diagnostics.stacktrace.aspx
【讨论】:
您可以在 System.Diagnostics.Process.GetCurrentProcess().Threads 上循环,并为每个 Thread 创建一个 StackTrace 对象,其 .ctor 将 Thread 作为其参数。
【讨论】: