It is possible to get stacktrace using System.Diagnostics.StackTrace, but thread has to be suspended. Suspend and Resume function are obsolete, so I expect that better way exists.
6 Answers
NB: Skip to the bottom of this answer for an update.
Here's what's worked for me so far:
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;
}
If it deadlocks, the deadlock is automatically freed and you get back a null trace. (You can then call it again.)
I should add that after a few days of testing, I've only once been able to create a deadlock on my Core i7 machine. Deadlocks are common, though, on single-core VM when the CPU runs at 100%.
Update: This approach works only for .NET Framework. In .NET Core and .NET 5+, Suspend
and Resume
cannot be called, so you must use an alternative approach such as Microsoft's ClrMD library. Add a NuGet reference to the Microsoft.Diagnostics.Runtime package; then you can call DataTarget.AttachToProcess
to obtain information about threads and stacks. Note that you cannot sample your own process, so you must start another process, but that is not difficult. Here is a basic Console demo that illustrates the process, using a redirected stdout to send the stack traces back to the host:
using Microsoft.Diagnostics.Runtime;
using System.Diagnostics;
using System.Reflection;
if (args.Length == 3 &&
int.TryParse (args [0], out int pid) &&
int.TryParse (args [1], out int threadID) &&
int.TryParse (args [2], out int sampleInterval))
{
// We're being called from the Process.Start call below.
ThreadSampler.Start (pid, threadID, sampleInterval);
}
else
{
// Start ThreadSampler in another process, with 100ms sampling interval
var startInfo = new ProcessStartInfo (
Path.ChangeExtension (Assembly.GetExecutingAssembly().Location, ".exe"),
Process.GetCurrentProcess().Id + " " + Thread.CurrentThread.ManagedThreadId + " 100")
{
RedirectStandardOutput = true,
CreateNoWindow = true
};
var proc = Process.Start (startInfo);
proc.OutputDataReceived += (sender, args) =>
Console.WriteLine (args.Data != "" ? " " + args.Data : "New stack trace:");
proc.BeginOutputReadLine();
// Do some work to test the stack trace sampling
Demo.DemoStackTrace();
// Kill the worker process when we're done.
proc.Kill();
}
class Demo
{
public static void DemoStackTrace()
{
for (int i = 0; i < 10; i++)
{
Method1();
Method2();
Method3();
}
}
static void Method1()
{
Foo();
}
static void Method2()
{
Foo();
}
static void Method3()
{
Foo();
}
static void Foo() => Thread.Sleep (100);
}
static class ThreadSampler
{
public static void Start (int pid, int threadID, int sampleInterval)
{
DataTarget target = DataTarget.AttachToProcess (pid, false);
ClrRuntime runtime = target.ClrVersions [0].CreateRuntime();
while (true)
{
// Flush cached data, otherwise we'll get old execution info.
runtime.FlushCachedData();
foreach (ClrThread thread in runtime.Threads)
if (thread.ManagedThreadId == threadID)
{
Console.WriteLine(); // Signal new stack trace
foreach (var frame in thread.EnumerateStackTrace().Take (100))
if (frame.Kind == ClrStackFrameKind.ManagedMethod)
Console.WriteLine (" " + frame.ToString());
break;
}
Thread.Sleep (sampleInterval);
}
}
}
This is the mechanism that LINQPad 6+ uses to show live execution tracking in queries (with additional checks, metadata probing and a more elaborate IPC).

- 30,118
- 7
- 80
- 91
-
You might want to use a second `ManualResetEvent` to avoid targetThread.Resume() being executed and throwing an exception *every time*... if (!noDeadLockSafeGuard.WaitOne(200)) { try { targetThread.Resume(); } catch { } } – Vincent Van Den Berghe Jul 20 '12 at 13:09
-
4There is still a tiny risk for a deadlock left: If the runtime decides to suspend the main-thread between "ready.Wait()" and "targetThread.Suspend()", you might still have a deadlock since the fallback-Thread exited already. IMO you need to have a loop in the unlock-thread that is left only when the main-thread signals that it exited the function safely. – Andreas Feb 18 '13 at 11:25
-
4Thread.Suspend() and Thread.Resume() are marked as obsolete in the Framework, so anyone using Warnings As Errors will need to use ``#pragma warning disable 0618`` before the method and ``#pragma warning restore 0618`` afterwards in order to get this code to compile. – Warren Rumak Sep 09 '13 at 14:40
-
2Unfortunately, this technique is now obsolete: https://msdn.microsoft.com/en-us/library/t2k35tat(v=vs.110).aspx – Andrew Rondeau Apr 24 '17 at 14:44
-
@AndrewRondeau Is there an alternative? – void.pointer Jan 23 '22 at 23:41
-
@void.pointer : Well, that depends on what you're trying to do! Why do you want to get another thread's stack trace, and what are you going to do with it? In my case, I was trying to log when a thread held a lock too long. It was simple defensive programming to try to collect information in case a bug occurred in deployed code. My solution was very specific to the application's architecture and not generalizable. I'm pulling together notes to put on my blog; and if you can tell me more about why you want another thread's stack trace, I might have some helpful pointers. – Andrew Rondeau Jan 26 '22 at 02:49
-
(And, you probably know this, but if you break into Visual Studio's debugger, you can get all threads' stack traces!) – Andrew Rondeau Jan 26 '22 at 02:50
-
FYI, according to Microsoft, "there is no recommended alternative." See https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.stacktrace.-ctor?view=netframework-4.8#system-diagnostics-stacktrace-ctor(system-threading-thread-system-boolean) – Andrew Rondeau Jan 26 '22 at 03:45
-
I've updated the answer with an alternative that avoids use of Suspend and Resume – Joe Albahari Jan 27 '22 at 07:01
-
1Joe: Thanks, that's pretty cool. @void.pointer: I pulled together some notes about how I worked around this. Note that in the application I worked on in 2017, we wouldn't have shipped something like Joe's solution. See https://andrewrondeau.herokuapp.com/how_to_get_a_stack_trace_of_a_background_thread_in_net – Andrew Rondeau Feb 04 '22 at 03:51
This is an old Thread, but just wanted to warn about the proposed solution: The Suspend and Resume solution does not work - I just experienced a deadlock in my code trying the sequence Suspend/StackTrace/Resume.
The Problem is that StackTrace constructor does RuntimeMethodHandle -> MethodBase conversions, and this changes a internal MethodInfoCache, which takes a lock. The deadlock occurred because the thread I was examining also was doing reflection, and was holding that lock.
It is a pity that the suspend/resume stuff is not done inside the StackTrace constructor -then this problem could easily have been circumvented.

- 648
- 5
- 12
-
1Totally true - I've encountered deadlocks doing this. There does seem to be a workaround, though (see my answer). – Joe Albahari Mar 07 '12 at 04:11
Update 2022-04-28: This answer does only work in .NET Framework. It is not compatible with .NET Core and .NET Standard. Since we all need to migrate sooner or later, you should not use it in new code anymore.
As mentioned in my comment, the proposed solution above does still have a tiny probability for a deadlock. Please find my version below.
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();
}
}
}
I think, the ManualResetEventSlim "fallbackThreadReady" is not really necessary, but why risk anything in this delicate case?

- 1,997
- 21
- 35
-
1
-
-
Would you say this approach is deadlock proof? Edit: You mentioned a comment, but I wasn't sure if you were referring to a comment on the above solution provided by the OP. – Hatchling Jul 20 '16 at 22:43
-
2@Hatchling: I do not see any more possibilities for deadlocks. There might still be the possibility for one, but I never had one with this code. – Andreas Jul 21 '16 at 10:52
According to C# 3.0 in a Nutshell, this is one of the few situations where it is okay to call Suspend/Resume.

- 114,645
- 34
- 221
- 317
-
-
1Be careful not to introduce deadlocks tough. If you suspend a thread while it is holding a lock you need, you'll have a deadlock. The most common cause would probably be if the threads share a stream (e.g. writing to the console or similar). – Brian Rasmussen Feb 09 '09 at 14:34
-
2
It looks like this was a supported operation in the past, but unfortunately, Microsoft made this obsolete: https://msdn.microsoft.com/en-us/library/t2k35tat(v=vs.110).aspx

- 667
- 7
- 18
I think that if you want to do this without the cooperation of the target thread (such as by having it call a method that blocks it on a Semaphore or something while your thread does the stacktrace) you'll need to use the deprecated APIs.
A possible alternative is the use the COM-based ICorDebug interface that the .NET debuggers use. The MDbg codebase might give you a start:

- 21,988
- 13
- 81
- 109

- 333,147
- 50
- 533
- 760
-
1No, COM is not an option. Suspend/Resume feels way cleaner than COM stuff from .NET... – bh213 Nov 13 '08 at 09:41