Как получить трассировку стека нетокового потока? - PullRequest
30 голосов
/ 12 ноября 2008

Можно получить трассировку стека с помощью System.Diagnostics.StackTrace, но поток должен быть приостановлен. Функции Suspend и Resume устарели, поэтому я ожидаю, что существует лучший способ.

Ответы [ 6 ]

21 голосов
/ 07 марта 2012

Вот что у меня сработало:

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. В то же время взаимоблокировки характерны для одноядерной виртуальной машины, когда загрузка процессора составляет 100%.

16 голосов
/ 30 августа 2010

Это старый поток, но он просто хотел предупредить о предлагаемом решении: решение Suspend and Resume не работает - я только что столкнулся с тупиковой ситуацией в своем коде, пытаясь выполнить последовательность Suspend / StackTrace / Resume.

Проблема в том, что конструктор StackTrace выполняет преобразования RuntimeMethodHandle -> MethodBase, и это изменяет внутренний методInInfoCache, который принимает блокировку. Тупик возник из-за того, что нить, которую я изучал, также выполняла отражение и удерживала этот замок.

Жаль, что приостановка / возобновление не выполняется внутри конструктора StackTrace - тогда эту проблему можно было легко обойти.

11 голосов
/ 18 февраля 2013

Как уже упоминалось в моем комментарии, предлагаемое решение все еще имеет небольшую вероятность тупика. Пожалуйста, найдите мою версию ниже.

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" на самом деле не нужен, но зачем рисковать чем-либо в этом деликатном случае?

11 голосов
/ 09 февраля 2009

Согласно C # 3.0 в двух словах , это одна из немногих ситуаций, когда можно вызвать Suspend / Resume.

2 голосов
/ 24 апреля 2017

Похоже, это была поддерживаемая операция в прошлом, но, к сожалению, Microsoft сделала это устаревшей: https://msdn.microsoft.com/en-us/library/t2k35tat(v=vs.110).aspx

2 голосов
/ 12 ноября 2008

Я думаю, что если вы хотите сделать это без взаимодействия с целевым потоком (например, с помощью вызова метода, блокирующего его на семафоре, или чего-то еще, пока ваш поток выполняет трассировку стека), вам нужно использовать устаревшие API.

Возможной альтернативой является использование интерфейса ICorDebug на основе COM, используемого отладчиками .NET. Кодовая база MDbg может дать вам начало:

...