So basically you're facing an issue with a 3rd party library that doesn't nicely clean up when it should, and on top of that its spun up a bunch of foreground threads keeping your application running even when you want it terminated.
Example
As an example, an application like this
private static void Main(string[] args)
{
var t = new Thread(() =>
{
while (true)
{
}
}) {Name = "test"};
t.Start();
Console.WriteLine("Exited");
}
Will sit forever since t
is a foreground thread.
Let's just double check with process explorer. Here's an updated version of our demo app that we can pinvoke and get the native thread id.
internal class Program
{
[DllImport("kernel32.dll")]
static extern IntPtr OpenThread(uint dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
[DllImport("kernel32.dll")]
static extern bool TerminateThread(IntPtr hThread, uint dwExitCode);
[DllImport("kernel32.dll")]
static extern int GetCurrentThreadId();
private static void Main(string[] args)
{
var t = new Thread(() =>
{
Console.WriteLine(GetCurrentThreadId());
while (true)
{
}
}) {Name = "test"};
t.Start();
Console.WriteLine("Thread Id " + t.ManagedThreadId);
Console.WriteLine("Exited");
}
}
Gives me the native thread id. In process explorer I can see now:

Pretty clear that thread 8228 is spinning wildly because of my while loop, even though main has already exited
In general, User Rob Hardy is right. If you control your threads you should always keep track of things and manage them yourself, but I think you're in a pickle here since you don't have access to the thread handles.
Option 1 (but please don't)
You can try and kill everything (but that didn't really work for me when I tried it anyways), but I really wouldn't do this since it seems incredibly dangerous to do. Just to copy the information from the other post the example said this:
internal class Program
{
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
static extern IntPtr OpenThread(uint dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
static extern bool TerminateThread(IntPtr hThread, uint dwExitCode);
private static void Main(string[] args)
{
var t = new Thread(() =>
{
while (true)
{
}
}) {Name = "test"};
t.Start();
Console.WriteLine("Exited");
Thread.Sleep(TimeSpan.FromSeconds(2));
foreach (ProcessThread pt in Process.GetCurrentProcess().Threads)
{
IntPtr ptrThread = OpenThread(1, false, (uint)pt.Id);
if (AppDomain.GetCurrentThreadId() != pt.Id)
{
try
{
TerminateThread(ptrThread, 1);
Console.Out.Write(". Thread killed.\n");
}
catch (Exception e)
{
Console.Out.WriteLine(e.ToString());
}
}
else
Console.Out.Write(". Not killing... It's the current thread!\n");
}
}
}
But again, that didn't stop the process for me. Maybe .net is waiting for proper exiting of the threads and not just forceful native exits? I don't know. I'm just showing that you can technically (according to the link) can kill threads from ProcessThread
, if you REALLY wanted to (but please don't)
Option 2
A more reasonable option is to add an explicit Exit
call with a code after all your cleanup (exit code 0 is customary to indicate a clean exit)
private static void Main(string[] args)
{
var t = new Thread(() =>
{
while (true)
{
}
}) {Name = "test"};
t.Start();
Console.WriteLine("Exited");
Environment.Exit(0);
}
This worked for me.
Option 3
The third option, if you can, is to just fix the library and have it either create background threads or let it clean up properly. This would be the best option since you won't be leaving any potential corruptible side effects open by not cleaning up the 3rd party items. Though, I'm assuming you aren't doing this because the library is closed source.