This is the twentieth eighth part of the .NET Inside Out series. For your convenience you can find other parts in the table of contents in Part 1 – Virtual and non-virtual calls in C#
Last time we learned how to reroute an existing thread to some other code. Today we want to terminate the thread.
First method: there is a method Thread.Abort
which throws PlatformNotSupportedException
starting with .NET Core. Too bad.
Second method: we can go with TerminateThread
. The problem is there is no clear equivalent in non-Windows world. It also has some drawback which we’ll cover later.
Third method: we can go with ExitThread
or pthread_exit
To do that we need to reroute the thread the same way we did in the last part and call this method. However, it suffers from the same issue as method 2 — .NET doesn’t understand what happened. Once we exit the thread this way .NET just cannot handle it correctly anymore. It means that if you try calling terminatedThread.join
you get a deadlock. You could work that around by setting IsBackground = true
so it doesn’t stop the process from exiting but it’s still bad.
Fourth method: we can simulate exiting. We suspend the thread, take its stack, examine it and exit all the things. That’s nearly impossible as we’d need to analyze the whole code with all dependencies. Even reading the machine code without symbols may be impossible in x86, not to mention that we might need to solve the halting problem.
Fifth method: we unwind the stack and then clear the thread. This is the only working method I’m aware of.
Unwinding stack
First, we need to hijack the thread constructor. Just before it starts executing the thread function we take all important registers (rbp, rsp, rsi, rbx, rdx) and save them on the side (so we do the “set jump” part). Next, we call the thread function.
Next, once we want to stop the thread, we restore all important registers and let it carry on. This way we remove all the stack frames from the stack and continue as if the main function finished.