.net - ¿Cómo obtener la stacktrace del hilo no actual?
c# get call stack programmatically (6)
Es posible obtener stacktrace utilizando System.Diagnostics.StackTrace, pero el hilo debe suspenderse. Las funciones Suspender y Reanudar son obsoletas, por lo que espero que exista una mejor manera.
Creo que si quieres hacer esto sin la cooperación del hilo de destino (como hacer que llame a un método que lo bloquea en un semáforo o algo así mientras tu hilo hace el stacktrace) necesitarás usar las API en desuso.
Una posible alternativa es el uso de la interfaz ICorDebug basada en COM que usan los depuradores .NET. La base de código MDbg podría darle un comienzo:
De acuerdo con C # 3.0 en Nutshell , esta es una de las pocas situaciones en las que está bien llamar a Suspender / Reanudar.
Este es un tema viejo, pero solo quería advertir sobre la solución propuesta: la solución Suspender y Reanudar no funciona. Acabo de experimentar un punto muerto en mi código al probar la secuencia Suspender / Pilatizar / Reanudar.
El problema es que el constructor de StackTrace hace conversiones de RuntimeMethodHandle -> MethodBase, y esto cambia una MethodInfoCache interna, que toma un bloqueo. El punto muerto se produjo porque el hilo que estaba examinando también estaba haciendo un reflejo, y estaba sosteniendo ese bloqueo.
Es una lástima que las cosas de suspender / reanudar no se realicen dentro del constructor de StackTrace; entonces, este problema podría haberse eludido fácilmente.
Esto es lo que funcionó para mí hasta ahora:
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;
}
Si se bloquea, el punto muerto se libera automáticamente y obtienes un rastro nulo. (Puede llamarlo nuevamente).
Debo añadir que después de unos días de pruebas, solo una vez he podido crear un punto muerto en mi máquina Core i7. Los bloqueos son comunes, sin embargo, en la VM de un solo núcleo cuando la CPU funciona al 100%.
Como mencioné en mi comentario, la solución propuesta aún tiene una pequeña probabilidad de un punto muerto. Por favor encuentra mi versión a continuación.
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();
}
}
}
Creo que el ManualResetEventSlim "fallbackThreadReady" no es realmente necesario, pero ¿por qué arriesgar algo en este delicado caso?
Parece que esta era una operación admitida en el pasado, pero desafortunadamente Microsoft lo dejó obsoleto: https://msdn.microsoft.com/en-us/library/t2k35tat(v=vs.110).aspx