Как получить нетоковый поток stacktrace?

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

Ответ 1

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

Ответ 2

Вот что мне помогло:

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

Ответ 3

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

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

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

Ответ 4

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

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

Ответ 5

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

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