I recently solved a problem that the community might run into, so I think I’ll share my solution. I had a process (in my case a Windows service, but this concept applies to Console or WinForms apps as well) that dispatched work to the threadpool. There are different types of work, and each type should not have to wait for all of another type to finish, so I use the threadpool for concurrent execution (I have to wait for a database, so I actually do see performance gains), but if there is 3 hours worth of type A work to do, type B should not have to wait that 3 hours to begin. The situation worked fine until I needed to stop my service. When you stop a process, it stops. It doesn’t wait for catch and finally blocks to execute, either. If there was work in progress, that work was left in a corrupted state. That possibility is not acceptable for a production application. I need to be able to stop or wait for the work before I allow the service to complete stopping. My solution was that for each new work item that was started in the threadpool, I added that thread to an ArrayList, and when the work was finished, I removed it. When I needed to stop, I walked through each thread and called Abort() and Join(). This threw a ThreadAbortException down that thread allowing my normal cleanup code to execute, and Join() waited a specified period of time for that thread to finish and exit, then control returned to the main thread. After all the threads were finished, I would finish stopping. In this way, I could maintain control over work being done in the threadpool.
Here is a sample class that dispatches work (it makes the work run in its own AppDomain too).
public class Controller
{
public ArrayList threads = new ArrayList();
public Controller(){
// Just so we can see the activity.
Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
}
public void DoWork(){
try{
// Three might be running at the same time.
for(int i = 0; i < 3; i++){
ThreadPool.QueueUserWorkItem(new WaitCallback(Execute));
}
Thread.Sleep(20000);
Trace.WriteLine(threads.Count, this.ToString());
}finally{
foreach(Thread t in threads){
t.Abort();
t.Join();
}
}
}
private void Execute(object state){
AppDomainSetup setup = null;
AppDomain domain = null;
try{
threads.Add(Thread.CurrentThread);
setup = new AppDomainSetup();
setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
domain = AppDomain.CreateDomain(“Processor”, null, setup);
Processor proc = (Processor)domain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(Processor).FullName);
proc.Process();
}finally{
threads.Remove(Thread.CurrentThread);
if(domain != null)
AppDomain.Unload(domain);
}
}
}
And here is my sample Processor class:
[Serializable()]
public class Processor
{
public void Process(){
try{
Trace.WriteLine(“Beginning Processing”, this.ToString());
// Simulate a long-running process.
System.Threading.Thread.Sleep(20000);
}finally{
Trace.WriteLine(“Finished”, this.ToString());
// Close out database record an other cleanup.
}
}
}
This has solved my problem, and now when I sent work to the threadpool it’s not just a foreign country with no phones. I stay in control of those threads. The above code is a sample of the same concept I’m using. Feel free to use the sample. but it’s been simplified to protect the innocent, so make sure to fully test whatever you put into practice.