Как да получа проследяване на стека на нетекуща нишка?

Възможно е да получите stacktrace с помощта на System.Diagnostics.StackTrace, но нишката трябва да бъде спряна. Функциите за спиране и възобновяване са остарели, така че очаквам, че съществува по-добър начин.


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


Отговори (6)


Според C# 3.0 in a Nutshell, това е една от малкото ситуации, в които е добре да извикате Suspend /Продължи.

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(), все още може да имате блокиране, тъй като резервната нишка вече е излязла. IMO трябва да имате цикъл в отключващата нишка, която остава само когато основната нишка сигнализира, че е излязла безопасно от функцията. - person Andreas; 18.02.2013
comment
Thread.Suspend() и Thread.Resume() са маркирани като остарели в рамката, така че всеки, който използва предупреждения като грешки, ще трябва да използва #pragma warning disable 0618 преди метода и #pragma warning restore 0618 след това, за да накара този код да се компилира. - person Warren Rumak; 09.09.2013
comment
За съжаление тази техника вече е остаряла: msdn.microsoft .com/en-us/library/t2k35tat(v=vs.110).aspx - person Andrew Rondeau; 24.04.2017

Това е стара тема, но просто исках да предупредя за предложеното решение: Решението за спиране и възобновяване не работи - току-що получих задънена улица в моя код, опитвайки последователността Спиране/StackTrace/Възобновяване.

Проблемът е, че конструкторът на 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: Добавен израз за използване. Благодаря ви за подсказката. - 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

Мисля, че ако искате да направите това без сътрудничеството на целевата нишка (като например като я накарате да извика метод, който го блокира на Semaphore или нещо подобно, докато нишката ви прави проследяването на стека), ще трябва да използвате остарелите API.

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

person Michael Burr    schedule 12.11.2008
comment
Не, COM не е опция. Suspend/Resume се чувства много по-чист от COM неща от .NET... - person bh213; 13.11.2008