如何获取非当前线程的堆栈跟踪?
-
08-07-2019 - |
题
可以使用 System.Diagnostics.StackTrace 获取堆栈跟踪,但必须挂起线程。暂停和恢复功能已过时,因此我希望存在更好的方法。
解决方案
根据坚果壳中的C#3.0 ,这是少数情况可以接受的情况之一调用暂停/恢复。
其他提示
到目前为止,这对我有用:
StackTrace GetStackTrace (Thread targetThread)
{
StackTrace stackTrace = null;
var ready = new ManualResetEventSlim();
new Thread (() =>
{
// Backstop to release thread in case of deadlock:
ready.Set();
Thread.Sleep (200);
try { targetThread.Resume(); } catch { }
}).Start();
ready.Wait();
targetThread.Suspend();
try { stackTrace = new StackTrace (targetThread, true); }
catch { /* Deadlock */ }
finally
{
try { targetThread.Resume(); }
catch { stackTrace = null; /* Deadlock */ }
}
return stackTrace;
}
如果死锁,则自动释放死锁并返回空跟踪。 (然后你可以再次调用它。)
我应该补充说,经过几天的测试,我只能在我的Core i7机器上创建死锁。但是,当CPU以100%运行时,死锁在单核VM上很常见。
这是一个旧的线程,但只是想提出有关建议的解决方案的警告:暂停和恢复解决方案不起作用 - 我只是在我的代码中遇到了死锁,尝试序列Suspend / StackTrace / Resume。
问题是StackTrace构造函数执行RuntimeMethodHandle - > MethodBase转换,这会更改内部MethodInfoCache,它会锁定。发生了死锁,因为我正在检查的线程也正在进行反射,并持有该锁定。
很遗憾,挂起/恢复内容不是在StackTrace构造函数内部完成的 - 然而这个问题很容易被绕过。
正如我在评论中所提到的,建议的解决方案仍然存在很小的死锁概率。请在下面找到我的版本。
private static StackTrace GetStackTrace(Thread targetThread) {
using (ManualResetEvent fallbackThreadReady = new ManualResetEvent(false), exitedSafely = new ManualResetEvent(false)) {
Thread fallbackThread = new Thread(delegate() {
fallbackThreadReady.Set();
while (!exitedSafely.WaitOne(200)) {
try {
targetThread.Resume();
} catch (Exception) {/*Whatever happens, do never stop to resume the target-thread regularly until the main-thread has exited safely.*/}
}
});
fallbackThread.Name = "GetStackFallbackThread";
try {
fallbackThread.Start();
fallbackThreadReady.WaitOne();
//From here, you have about 200ms to get the stack-trace.
targetThread.Suspend();
StackTrace trace = null;
try {
trace = new StackTrace(targetThread, true);
} catch (ThreadStateException) {
//failed to get stack trace, since the fallback-thread resumed the thread
//possible reasons:
//1.) This thread was just too slow (not very likely)
//2.) The deadlock ocurred and the fallbackThread rescued the situation.
//In both cases just return null.
}
try {
targetThread.Resume();
} catch (ThreadStateException) {/*Thread is running again already*/}
return trace;
} finally {
//Just signal the backup-thread to stop.
exitedSafely.Set();
//Join the thread to avoid disposing "exited safely" too early. And also make sure that no leftover threads are cluttering iis by accident.
fallbackThread.Join();
}
}
}
我认为,ManualResetEventSlim" fallbackThreadReady"是不是真的有必要,但为什么在这个微妙的情况下冒任何风险?
我认为,如果您想在不与目标线程合作的情况下执行此操作(例如,在您的线程执行堆栈跟踪时,让其调用在信号量上阻止它的方法或其他方法),您将需要使用已弃用的 API。
一个可能的替代方案是使用 基于 COM 的 ICorDebug .NET 调试器使用的接口。MDbg 代码库可能会给您一个开始:
过去看起来这是一个受支持的操作,但不幸的是,微软已经过时了: https://msdn.microsoft.com/en-us/library/t2k35tat(v = vs.110).aspx