Есть ли способ получить трассировки стека для всех потоков в C#, например, java.lang.Thread.getAllStackTraces()?

В Java можно получить снимок трассировки стека всех запущенных потоков. Это сделано с java.lang.Thread.getAllStackTraces() (возвращается Map<Thread,StackTraceElement[]>).

Как это можно сделать с помощью.net?

8 ответов

Решение

Так что мне просто нужно было выяснить, как это сделать - я еще не широко использовал это решение в производстве, но есть относительно новая библиотека под названием ClrMd.

http://blogs.msdn.com/b/dougste/archive/2013/05/04/clrmd-net-crash-dump-and-live-process-inspection.aspx

Используя его, я могу подключиться к своему собственному процессу и получить трассировку стека для всех активных потоков. Используя это, когда обнаруживается тупик перед перезапуском нашего приложения, вот так:

var result = new Dictionary<int, string[]>();

var pid = Process.GetCurrentProcess().Id;

using (var dataTarget = DataTarget.AttachToProcess(pid, 5000, AttachFlag.Passive))
{
    string dacLocation = dataTarget.ClrVersions[0].TryGetDacLocation();
    var runtime = dataTarget.CreateRuntime(dacLocation);

    foreach (var t in runtime.Threads)
    {
        result.Add(
            t.ManagedThreadId,
            t.StackTrace.Select(f =>
            {
                if (f.Method != null)
                {
                    return f.Method.Type.Name + "." + f.Method.Name;
                }

                return null;
            }).ToArray()
        );
    }
}

var json = JsonConvert.SerializeObject(result);

zip.AddEntry("_threads.json", json);

Действительно важная вещь, чтобы заставить это работать от того же самого процесса, является AttachFlag.Passive

Если вы просто делаете DataTarget.AttachToProcess(pid, 5000), он сделает "инвазивное" присоединение, которое пытается приостановить процесс. Это вызывает исключение, когда вы пытаетесь присоединиться к своему собственному процессу, я полагаю, потому что вы не можете приостановить приложение, пытаясь подключиться из приложения или что-то в этом роде.

В любом случае, да, классные вещи.

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

Если вы хотите сделать это только для целей отладки, расширения SOS для WinDbg могут предоставить вам эту информацию.

Команда для запуска "*~e! Clrstack".

Внутри работающей программы на C# нет открытого способа перечислять управляемые потоки или искать их по ID. Даже если бы вы могли, получение трассировки стека в другом потоке, вероятно, потребовало бы его приостановки, что имеет некоторые риски побочных эффектов (посмотрите, почему это устарело).

Другая альтернатива - заручиться тем, как они известны, и сканировать их на досуге. Это возможно только в том случае, если вы явно создаете объекты потока, а не используете пул потоков.

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

Обновлен код для получения снимка всех трассировок стека, который использует ответ от @Joshua Evensen в качестве основы. Вам по-прежнему потребуется установить пакет NuGet для диагностики памяти CLR (ClrMD) . Этот фрагмент также включает дополнительный код для получения имен потоков, но это не требуется, если вам просто нужны трассировки стека.

      using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.Diagnostics.Runtime;

namespace CSharpUtils.wrc.utils.debugging
{
    public static class StackTraceAnalysis
    {
        public static string GetAllStackTraces()
        {
            var result = new StringBuilder();
            
            using (var target = DataTarget.CreateSnapshotAndAttach(Process.GetCurrentProcess().Id))
            {
                var runtime = target.ClrVersions.First().CreateRuntime();

                // We can't get the thread name from the ClrThead objects, so we'll look for
                // Thread instances on the heap and get the names from those.    
                var threadNameLookup = new Dictionary<int, string>();
                foreach (var obj in runtime.Heap.EnumerateObjects())
                {
                    if (!(obj.Type is null) && obj.Type.Name == "System.Threading.Thread")
                    {
                        var threadId = obj.ReadField<int>("m_ManagedThreadId");
                        var threadName = obj.ReadStringField("m_Name");
                        threadNameLookup[threadId] = threadName;
                    }
                }

                foreach (var thread in runtime.Threads)
                {
                    threadNameLookup.TryGetValue(thread.ManagedThreadId, out string threadName);
                    result.AppendLine(
                        $"ManagedThreadId: {thread.ManagedThreadId}, Name: {threadName}, OSThreadId: {thread.OSThreadId}, Thread: IsAlive: {thread.IsAlive}, IsBackground: {thread.IsBackground}");
                    foreach (var clrStackFrame in thread.EnumerateStackTrace())
                        result.AppendLine($"{clrStackFrame.Method}");
                }
            }

            return result.ToString();
        }
    }
}

Вы можете использовать ProcInsp, у которого есть веб-API для получения потоков с их стеками в JSON. Веб-API доступен по адресу /Process/%PID%/Threads (использовать GET запрос).

Отказ от ответственности: я разработчик ProcInsp. Инструмент находится под лицензией MIT и является бесплатным для использования.

Как говорит Mason of Words, это не представляется возможным изнутри самого управляемого кода. Не могли бы вы уточнить, зачем вам это нужно: может быть, есть лучшее решение?

Например, если вы присоединяетесь к процессу в Visual Studio и нажимаете "пауза", то в окне "Потоки" будут перечислены все управляемые потоки, а в окне "Stacktrace" может отображаться текущая трассировка стека для каждого потока. Будет ли этого достаточно?

Есть класс StackTrace

var trace = new System.Diagnostics.StackTrace(exception);

http://msdn.microsoft.com/en-us/library/system.diagnostics.stacktrace.aspx

Попробуй это

var proc = System.Diagnostics.Process.GetCurrentProcess();
var threads = proc.Threads;

var res = new Dictionary<Thread, StackTrace>();
foreach (Thread thread in threads)
{
    var stackTrace = new StackTrace(thread, true);
    res.Add(thread, stackTrace);
}

В результате вы получите словарь с отображением, которое вы хотите:)

Вы можете зациклить System.Diagnostics.Process.GetCurrentProcess(). Потоки и для каждого потока создать объект StackTrace с.ctor, который принимает Thread в качестве параметра.

Другие вопросы по тегам