Как получить трассировку стека нетекущего потока?

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


person bh213    schedule 12.11.2008    source источник


Ответы (6)


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

person Brian Rasmussen    schedule 09.02.2009
comment
Вот что я в итоге сделал. - person bh213; 09.02.2009
comment
Будьте осторожны, чтобы не попасть в тупик. Если вы приостановите поток, пока он удерживает нужную вам блокировку, у вас будет тупик. Наиболее частая причина, вероятно, будет в том, что потоки совместно используют поток (например, запись в консоль или что-то подобное). - person Brian Rasmussen; 09.02.2009
comment
Это устарело сейчас? Есть лучший способ сделать это? - person Caleb Seelhoff; 10.09.2020

Вот что у меня сработало до сих пор:

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%.

person Joe Albahari    schedule 07.03.2012
comment
Вы можете использовать второй ManualResetEvent, чтобы избежать выполнения targetThread.Resume () и генерирования исключения каждый раз ... if (! NoDeadLockSafeGuard.WaitOne (200)) {try {targetThread.Resume ( ); } поймать { } } - person Vincent Van Den Berghe; 20.07.2012
comment
По-прежнему существует небольшой риск возникновения тупиковой ситуации: если среда выполнения решит приостановить основной поток между ready.Wait () и targetThread.Suspend (), у вас все еще может быть тупик, поскольку резервный поток уже завершился. ИМО, вам нужно иметь цикл в потоке разблокировки, который остается только тогда, когда основной поток сигнализирует о том, что он безопасно вышел из функции. - person Andreas; 18.02.2013
comment
Thread.Suspend () и Thread.Resume () помечены как устаревшие в Framework, поэтому любой, кто использует предупреждения как ошибки, должен будет использовать #pragma warning disable 0618 перед методом и #pragma warning restore 0618 после него, чтобы этот код скомпилировался. - person Warren Rumak; 09.09.2013
comment
К сожалению, этот метод сейчас устарел: msdn.microsoft .com / ru-ru / library / t2k35tat (v = vs.110) .aspx - person Andrew Rondeau; 24.04.2017

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

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

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

person Dirk Bonné    schedule 30.08.2010
comment
Совершенно верно - я сталкивался с тупиками при этом. Однако, похоже, есть обходной путь (см. Мой ответ). - person Joe Albahari; 07.03.2012

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

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

person Andreas    schedule 18.02.2013
comment
NB: ManualResetEventSlim - это IDisposable - person Mark Sowul; 31.01.2014
comment
@MarkSowul: добавлен оператор using. Спасибо за подсказку. - person Andreas; 02.03.2015
comment
Вы бы сказали, что этот подход доказывает тупиковую ситуацию? Изменить: вы упомянули комментарий, но я не был уверен, имеете ли вы в виду комментарий к вышеуказанному решению, предоставленному OP. - person Hatchling; 21.07.2016
comment
@Hatchling: я больше не вижу возможности зайти в тупик. Может быть, еще есть возможность для одного, но у меня никогда не было такого с этим кодом. - person Andreas; 21.07.2016

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

person Andrew Rondeau    schedule 24.04.2017

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

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

person Michael Burr    schedule 12.11.2008
comment
Нет, COM - это не вариант. Приостановка / возобновление кажется намного чище, чем вещи COM из .NET ... - person bh213; 13.11.2008