Respuesta

9

Según C# 3.0 in a Nutshell, esta es una de las pocas situaciones en las que es aceptable llamar suspender/reanudar.

+0

Eso es lo que terminé haciendo. – bh213

+0

Tenga cuidado de no introducir puntos muertos difíciles. Si suspendes un hilo mientras está sosteniendo un bloqueo que necesitas, tendrás un punto muerto. La causa más común probablemente sería si los subprocesos comparten una secuencia (por ejemplo, escribir en la consola o similar). –

2

Creo que si quieres hacer esto sin la cooperación del hilo de destino (por ejemplo, haciendo que llame a un método que lo bloquea en un semáforo o algo así mientras tu hilo hace el stacktrace) tendrás que usar las API desaprobadas.

Una posible alternativa es el uso de la interfaz COM-based ICorDebug que utilizan los depuradores .NET. El código base MDBG podría darle un comienzo:

+1

No, COM no es una opción. Suspender/Reanudar se siente mucho más limpio que las cosas COM de .NET ... – bh213

14

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 intentar la secuencia Suspender/Pilatizar/Reanudar.

El problema es que el constructor de StackTrace realiza 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.

+0

Totalmente cierto: me he encontrado con deadlocks haciendo esto. Sin embargo, parece haber una solución alternativa (ver mi respuesta). –

19

Esto es lo que ha funcionado 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 al principio, el punto muerto se libera automáticamente y volver un rastro nulo. (Puede llamarlo de nuevo.)

Debo añadir que después de unos días de prueba, solo una vez he podido crear un interbloqueo 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%.

+0

Es posible que desee utilizar un segundo 'ManualResetEvent' para evitar targetThread.Reanudar() se ejecuta y lanza una excepción * cada vez * ... if (! NoDeadLockSafeGuard.WaitOne (200)) { try {targetThread.Resume(); } catch {} } –

+3

Todavía hay un pequeño riesgo de que quede un punto muerto: si el tiempo de ejecución decide suspender el hilo principal entre "ready.Wait()" y "targetThread.Suspend()", es posible que todavía tenga un punto muerto desde que el hilo de reserva ya salió. IMO necesita tener un bucle en el hilo de desbloqueo que queda solo cuando el hilo principal indica que salió de la función de forma segura. – Andreas

+3

Thread.suspend() y Thread.Resume() están marcadas como obsoletas en el marco, por lo que cualquier persona que utilice advertencias como errores tendrá que utilizar '' #pragma warning desactivar 0618'' antes de que el método y ' '#pragma warning restore 0618'' después para obtener este código para compilar. –

10

Como mencioné en mi comentario, la solución propuesta todavía 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, la ManualResetEventSlim "fallbackThreadReady" no es realmente necesario, pero ¿por qué arriesgar nada en este caso delicada?

+0

NB: ManualResetEventSlim es IDisposable –

+0

@MarkSowul: se agregó una sentencia using . gracias por la pista. – Andreas

+0

¿Diría que este enfoque es una prueba de estancamiento? Editar: mencionó un comentario, pero no estaba seguro de si se refería a un comentario sobre la solución anterior proporcionada por el OP. – Hatchling

Cuestiones relacionadas