Click here to Skip to main content
15,867,942 members
Articles / Programming Languages / C#
Article

Cancellable Thread Pool

Rate me:
Please Sign up or sign in to vote.
3.52/5 (8 votes)
15 Dec 20046 min read 96.4K   920   36   13
A replacement ThreadPool with Abort and Resume functionality.

Introduction

Modern applications need to be aware of much more than their own context, and in particular, need to be aware of the power constraints of the machine that they run upon. More and more users run on laptops with battery constraints and we should program to allow them to conserve battery as it drains away.

The ThreadPool is a great scheme for asynchronous programming, but what about when you need to abort or suspend the Threads inside it? This class uses the WaitItem interface of the ThreadPool allowing you to pretty much replace this and provides extra functionality to allow Abort and Restart.

Background

I work on a laptop, and at the end of the day, I hibernate it. Once I get home, I attach to a different network on a different network adapter. Unfortunately, while working on an application with background asynchronous WebRequest/WebResponses, I resumed the machine to Exceptions being thrown from deep inside the .NET framework in a thread that I had no chance of wrapping in a try/catch block (possibly, the same one that affects Visual Studio). I tried changing back to synchronous calls but that blocked the GUI and the exceptions still came. I needed a way to abort my WebRequest before suspending the machine and run asynchronously.

I decided that the best way to do this was to run the call synchronously in another thread. A thread can be aborted before the machine suspends. A thread doesn't block the GUI. A thread can even come in a ThreadPool. Unfortunately, a System.Threading.ThreadPool provides no access to the underlying thread(s), so there is no way to stop the thread once started.

Initially, I did not set out to replicate the ThreadPool, I just wanted a simple way of starting and stopping a process running in a thread. That version took a delegate which was started in a thread and returned a result. However, that version would start 10-20 simultaneous threads together in my application, so I looked at queuing the work and using a single thread to run more than one delegate. After referring to the ThreadPool interface and documentation, it became clear that I was redesigning the wheel; so, I took the relevant parts of the System.Threading.ThreadPool interface, and re-implemented.

There are some methods of the ThreadPool interface which relate to the number of Threads inside the pool, that I have chosen not to implement. According to the MSDN documentation, the standard ThreadPool will keep a minimum number of threads running all the time. I have instead chosen to drop each and every thread if there is no work remaining, so that it is possible to end up with none running. I see no point in keeping them around if I only use the ThreadPool for 5 seconds of processing every 30 minutes. See below for more details on this topic.

Update

This project has been substantially re-written to include a ThreadStartEvaluator abstract class. Should you wish to change the logic for starting and stopping threads, then simply inherit from this base class and pass your new class into the constructor of the thread pool.

Using the code

Although your code can work perfectly well if you move from a System.Threading.ThreadPool object to a CancellableThreadPool, you should be aware that your threads are more likely to receive an abort signal. In order to provide the best possible code, we need to cover critical sections with try/catch blocks looking for ThreadAbortException. These sections could simply perform cleanup of allocated objects, rather than just relying on garbage collection, or might abort other tasks such as existing WebRequest/WebResponses, e.g.:

C#
public class ExampleThread()
{
    WebRequest wr = ...;
    public void ThreadMethod()
    {
    try
        {
            wr.GetResponse(...);
        }
        catch (ThreadAbortException e)
        {
            //Cancel all objects and reset so that we can be called again cleanly
            wr.Dispose;
            //Finish the Method ASAP.
            return;
        }
    }
}

Adding Cancelable thread pool to a new application

Start by adding the CancellableThreadPool to your main application logic layer (this could be your main form), and constructing with the maximum number of queued items per thread.

C#
CancellableThreadPool _threads = new CancellableThreadPool(...); 

As you can see, the demo application uses a default max queue length of 2 (two). If you wish to have an application that will never queue a process and always allocates a new thread, specify 0 (zero). Alternatively, a large number (e.g., 65535) will give you a queue that always runs a single process at a time, but if you stop and restart, you are not guaranteed to process in the same order.

Next, define a method with a signature capable of being used as a WaitCallback delegate, e.g.:

C#
public void Update(object state)
...

To start a process, use the following, where demo is the name of the object that holds the method,

C#
ThreadDemo demo = new ThreadDemo(...);
_threads.QueueWorkItem(new WaitCallback(demo.Update) );

Supporting Threads in your application

Now that you have your code running multi-threadedly, things are going great until you try to update your System.Windows.Form Gui. This example avoids the problem by refreshing from a timer and pulling the data out of the objects, all from the thread which created the GUI objects. This is not an ideal example.

Instead, I recommend adding methods to your Form which provide simple functionality. This example could have included:

C#
internal void ThreadStatus(ThreadDemo demo, string message)

The public/internal method then takes care of checking that we are on the correct Thread, and delegates the update to a private method, i.e.:

C#
internal void ThreadStatus(ThreadDemo demo, string message)
{
    if (this.InvokeRequired == true)
        this.Invoke(new ThreadStatusDelegate(realThreadStatus),
            new object[2] {demo,message});
    else
        realThreadStatus(demo, message);
}

private delegate void ThreadStatusDelegate(ThreadDemo demo, string status);
private void realThreadStatus(ThreadDemo demo, string message)
{
    GetListItem(demo).Text = message;
}

Code Features

This demonstration features a ThreadDemo object that is trying to count up by 50. When restarted, it looks at its current count and adds 50, which defines where it will stop counting. Should an error occur in the abort and restart of the Thread, you will see threads that count in excess of 50. However, when a ThreadAbortException is detected then the counter is reset to zero. This scenario is designed to demonstrate one method of resetting for use in more complicated processes.

CancellableThreadPool

The main code for reuse is in the CancellableThreadPool.cs file. The complexity of this class is in the ThreadRunner method. This method works by extracting a queued work item and performing the delegate inside it. You are responsible for the error handling within your delegate. Should you allow an exception to propagate outside of your control, then the thread will terminate to avoid any later possible complications and help advance garbage collection. This whole method is Run by the threads started by this class.

ThreadStartEvaluator and derived classes

This code is used to determine when to start and stop threads. Override the EvaluateThreadStartStop method to determine when new Threads should be started and stopped.

ThreadRunInfo

This class allows the state object to be passed to running Threads just as the System.Threading.ThreadPool does.

Points of Interest

With the ThreadStartEvaluatorbyQueueSize implementation, the number of running threads is dependant on the number of items in queue. You can set the maximum number of queued items per thread with the constructor as described above. This differs from the System.Threading.ThreadPool interface, which,

  1. Can be changed after construction,
  2. Has a minimum number of threads running at all times.

Does anybody have any scenarios where these differences would cause any problems?

History

  • v0.2a - 16th Dec 2004 - Rewrote to allow plug-in logic for controlling thread starting.
  • V0.1a - 13th Sep 2004. Initial release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
United Kingdom United Kingdom
** Apologies but my daughter was born in October 2004, and so coding now comes second. My reponses tend to take a lot longer**

I've been coding since I got my first ZX Spectrum. From Basic to assembly, through C,C++ and arriving at C#. On the way I've throughly enjoyed Perl, Lisp and XML.

I find I can make the intellectual leap to understand the problem, I love big picture designs, patterns and reuse. I may be addicted to abstract classes Smile | :) GOF has a lot to answer for. I miss delete() even though I spent too much time finding the leaks.

My favourite part of coding is in UI design because of the complexity, the event driven nature, and the fact its (virtually) tactile. I hate GUI's that don't follow system guidelines, don't resize, and don't display properly when you change system colour and font.

Comments and Discussions

 
GeneralSimpler approach : Using ThreadPool and Abort Threads Pin
munshisoft27-Dec-05 22:47
munshisoft27-Dec-05 22:47 
GeneralProblem in function EvaluateThreadMaybeAbort Pin
Jeffrey Scott Flesher19-Dec-04 15:51
Jeffrey Scott Flesher19-Dec-04 15:51 
Generalthread.Abort Pin
Member 46184417-Dec-04 3:23
Member 46184417-Dec-04 3:23 
GeneralThread safety Pin
Christoph Walcher15-Dec-04 1:18
Christoph Walcher15-Dec-04 1:18 
GeneralRe: Thread safety Pin
Al Gardner15-Dec-04 2:20
Al Gardner15-Dec-04 2:20 
GeneralSome observations ... Pin
Daniel_Hochee30-Nov-04 11:41
Daniel_Hochee30-Nov-04 11:41 
GeneralRe: Some observations ... Pin
Daniel_Hochee30-Nov-04 12:44
Daniel_Hochee30-Nov-04 12:44 
GeneralRe: Some observations ... Pin
Al Gardner30-Nov-04 22:20
Al Gardner30-Nov-04 22:20 
GeneralRe: Some observations ... Pin
Al Gardner1-Dec-04 0:56
Al Gardner1-Dec-04 0:56 
GeneralRe: Some observations ... Pin
Richard Schneider20-Dec-04 1:14
Richard Schneider20-Dec-04 1:14 
QuestionIs anybody interested in the (small) class for Power Management/Status? Pin
Al Gardner13-Sep-04 1:42
Al Gardner13-Sep-04 1:42 
AnswerRe: Is anybody interested in the (small) class for Power Management/Status? Pin
swpvdijk13-Sep-04 3:08
swpvdijk13-Sep-04 3:08 
GeneralRe: Is anybody interested in the (small) class for Power Management/Status? Pin
Al Gardner13-Sep-04 8:11
Al Gardner13-Sep-04 8:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.