24

Możliwe jest pobranie stacktrace za pomocą System.Diagnostics.StackTrace, ale wątek musi zostać zawieszony. Funkcje zawieszenia i wznowienia są przestarzałe, więc spodziewam się, że istnieje lepszy sposób.Jak zdobyć stos przejściowy wątku?

Odpowiedz

9

Według C# 3.0 in a Nutshell, jest to jedna z nielicznych sytuacji, kiedy jest dobrze, aby zadzwonić do wstrzymania/wznowienia.

+0

Tak właśnie zrobiłem. – bh213

+0

Uważaj, aby nie wprowadzić ostrych zakleszczeń. Jeśli zawiesisz wątek podczas trzymania blokady, będziesz potrzebować impasu. Najczęstszą przyczyną może być sytuacja, w której wątki współużytkują strumień (np. Zapis na konsoli lub podobne). –

2

Myślę, że jeśli chcesz to zrobić bez współpracy z docelowym wątkiem (np. Poprzez wywołanie metody blokującej go na semaforze lub czymś, gdy twój wątek wykonuje ślad stosu), musisz użyć przestarzałe interfejsy API.

Ewentualną alternatywą jest użycie interfejsu COM-based ICorDebug używanego przez debugery .NET. MDbg codebase może dać początek:

+1

Nie, COM nie jest rozwiązaniem. Suspend/Resume wydaje się bardziej czysty niż produkty COM z .NET ... – bh213

14

To jest stara nitka, ale chciałem tylko ostrzec o proponowanym rozwiązaniu: rozwiązanie Suspend and Resume nie działa - po prostu doświadczyłem zakleszczenia w moim kodzie próbując sekwencji Suspend/StackTrace/Resume.

Problem polega na tym, że konstruktor StackTrace wykonuje RuntimeMethodHandle -> konwersji MethodBase, a to zmienia wewnętrzną metodę MethodInfoCache, która przyjmuje blokadę. Zakleszczenie nastąpiło, ponieważ wątek, który badałem również robił odbicie i trzymał ten zamek.

Szkoda, że ​​zawieszanie/wznawianie pracy nie jest wykonywane wewnątrz konstruktora StackTrace - później ten problem mógł zostać ominięty.

+0

Zupełnie prawdziwa - spotkałem się z impasami. Wydaje się jednak, że istnieje obejście (zobacz moją odpowiedź). –

19

Oto, co pracował dla mnie do tej pory:

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

Jeśli zakleszczenia, impas zostanie automatycznie zwolniona i wrócisz null śladu. (Możesz potem zadzwonić jeszcze raz.)

Powinienem dodać, że po kilku dniach testowania, tylko raz udało mi się stworzyć zakleszczenie na mojej maszynie Core i7. Zakleszczenia są powszechne w przypadku jednordzeniowej maszyny wirtualnej, gdy procesor działa na 100%.

+0

Możesz chcieć użyć drugiego 'ManualResetEvent', aby uniknąć targetThread.Wznów() jest wykonywany i wyrzucanie wyjątku * za każdym razem * ... , jeśli (! NoDeadLockSafeGuard.WaitOne (200)) { try {celThread.Resume(); } catch {} } –

+3

Istnieje jeszcze niewielkie ryzyko wystąpienia impasu: Jeśli środowisko wykonawcze zdecyduje się zawiesić główny wątek pomiędzy "ready.Wait()" i "targetThread.Suspend()", nadal możesz mieć impas od czasu upadku - wątek już wyszedł. IMO musisz mieć pętlę w wątku odblokowującym, która pozostaje tylko wtedy, gdy główny wątek sygnalizuje, że bezpiecznie opuścił funkcję. – Andreas

+3

Thread.Suspend() i Thread.Resume() są oznaczone jako przestarzałe w Założeniach koncepcyjnych, więc ktoś przy użyciu ostrzeżenia jako błędy trzeba będzie użyć '#pragma warning disable 0618'' przed metody i ' '#pragma warning restore 0618'' następnie, aby skompilować ten kod. –

10

Jak wspomniano w moim komentarzu, proponowane rozwiązanie nadal ma niewielkie prawdopodobieństwo wystąpienia impasu. Poniżej znajdziesz moją wersję.

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

jak sądzę, ManualResetEventSlim „fallbackThreadReady” nie jest naprawdę konieczne, ale dlaczego ryzykować niczego w tej delikatnej sprawie?

+0

UWAGA: ManualResetEventSlim jest IDisposable –

+0

@MarkSowul: Dodano instrukcję użycia . Dziękuję za podpowiedź. – Andreas

+0

Czy można powiedzieć, że to podejście jest odporne na zakleszczenia? Edycja: Wspomniałeś o komentarzu, ale nie byłem pewien, czy odnosiłeś się do komentarza do powyższego rozwiązania dostarczonego przez PO. – Hatchling