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

ThreadQueue -- A queue for threads that allows asynchronous execution and a time limit

Rate me:
Please Sign up or sign in to vote.
4.14/5 (9 votes)
6 Dec 2006CPOL2 min read 47.3K   248   41   10
Describes my ThreadQueue class and related classes.

Introduction

This article documents my ThreadQueue and ThreadQueueItem classes. Together, these classes and the one (or more) you derive from ThreadQueueItem allow you to easily spin off a number of threads, controlling how many to execute at a time, and killing a thread if it runs too long. And if need be, you can kill all the threads at once. The class you derive from ThreadQueueItem may also include the code to exit the thread neatly rather than have it killed outright.

Background

I use this as part of a Windows service that needs to send data to another system. Each item takes about a minute to send, and I limit it to two minutes. Using this technique, I can have several going at once, kill any that take too long, and if I stop the service, I can kill them all before doing so.

Using the code

The included files are:

  • LogFileIgnoreAttribute.cs
  • ThreadAbortedEventArgs.cs
  • ThreadAbortingEventArgs.cs
  • ThreadCompleteEventArgs.cs
  • ThreadEventArgs.cs
  • ThreadMessageEventArgs.cs
  • ThreadOverdueEventArgs.cs
  • ThreadQueue.cs
  • ThreadQueueDemo.cs
  • ThreadQueueItem.cs
  • ThreadStartingEventArgs.cs
  • ThreadStatus.cs
  • ThreadWaitingEventArgs.cs

Extract them from the zip and add them to your project. (Well, not the demo file, unless you want to build the demo.)

Deriving your own ThreadQueueItem

Here is the definition of DemoThread from the demo:

C#
namespace ThreadQueueDemo
{
    public partial class DemoThread : PIEBALD.Types.ThreadQueueItem
    {
        /* A Really Useful thread will need some data, 
           an int will suffice here */
        private int howmany ;
        
        /* This is how Abort will tell Run to exit neatly */
        private bool aborting = false ;
        
        /* A new constructor to call the base 
           constructor and set the int field */
        public DemoThread
        (
            string                          Name
        ,
            System.Threading.ThreadPriority Priority
        ,
            System.TimeSpan                 TimeLimit
        ,
            PIEBALD.Types.ThreadQueue       Queue
        ,
            int                             HowMany
        ) : base ( Name , Priority , TimeLimit , Queue )
        {
            this.howmany = HowMany ;
            
            return ;
        }
    }
}

You must write an override for Run to do whatever it is that needs to be done. In this case, the Run method is just raising the ThreadMessage event, on the queue to which it belongs, the given number of times, pausing between iterations. Because there's a loop, implementing a scheme to abort the process is easily accomplished with a boolean flag.

C#
namespace ThreadQueueDemo
{
    public partial class DemoThread : PIEBALD.Types.ThreadQueueItem
    {
        /* Run is the method that gets executed by the thread */
        public override void
        Run
        (
        )
        {
            for ( int i = 0 ; !this.aborting && i < this.howmany ; i++ )
            {
                RaiseThreadMessage ( this.Name ) ;
                System.Threading.Thread.Sleep ( 100 ) ;
            }
            
            return ;
        }
    }
}

The Abort method is overridden to toggle the flag and wait for the thread to stop.

C#
namespace ThreadQueueDemo
{
    public partial class DemoThread : PIEBALD.Types.ThreadQueueItem
    {
        /* Abort is the method that tells Run to exit neatly */
        public override void
        Abort
        (
        )
        {
            this.aborting = true ;
            
            while ( this.State == PIEBALD.Types.ThreadStatus.Running )
            {
                System.Threading.Thread.Sleep ( 100 ) ;
            }
            
            base.Abort() ;
            
            return ;
        }
    }
}

Nothing else need be overridden, the base ThreadQueueItem and ThreadQueue will handle the rest.

Running your threads

Here is the definition of Main for ThreadQueueDemo from the demo. It has the burden of instantiating the threads and passing them to the ThreadQueue. All the real work is performed in the background.

C#
namespace ThreadQueueDemo
{
    public partial class
    ThreadQueueDemo
    {
        [System.STAThread]
        public static int
        Main
        (
            string[] args
        )
        {
            try
            {
                /* Step one: Instantiate a ThreadQueue 
                  (a limit of five threads at a time) */
                PIEBALD.Types.ThreadQueue queue = 
                        new PIEBALD.Types.ThreadQueue ( 5 , false ) ;

                /* Step two: Register event handlers (as desired) */
                queue.OnThreadAborted  += new 
                  PIEBALD.Types.Event.ThreadAborted  ( HandleThreadEvent ) ;
                queue.OnThreadAborting += new 
                  PIEBALD.Types.Event.ThreadAborting ( HandleThreadEvent ) ;
                queue.OnThreadComplete += new 
                  PIEBALD.Types.Event.ThreadComplete ( HandleThreadEvent ) ;
                queue.OnThreadMessage  += new 
                  PIEBALD.Types.Event.ThreadMessage  ( HandleThreadEvent ) ;
                queue.OnThreadOverdue  += new 
                  PIEBALD.Types.Event.ThreadOverdue  ( HandleThreadEvent ) ;
                queue.OnThreadStarting += new 
                  PIEBALD.Types.Event.ThreadStarting ( HandleThreadEvent ) ;
                queue.OnThreadWaiting  += new 
                  PIEBALD.Types.Event.ThreadWaiting  ( HandleThreadEvent ) ;
                
                /* Step three: Instantiate and initialize 
                   the threads and populate an array with them */
                /* This demo simply makes twenty-six threads, 
                   each named with a letter                */
                string names = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ;
            
                DemoThread[] threads = new DemoThread [ names.Length ] ;
                
                for ( int i = 0 ; i < names.Length ; i++ )
                {
                    threads [ i ] = new DemoThread
                    ( 
                        /* The name for the thread */
                        names.Substring ( i , 1 ) 
                    ,
                        /* The priority for the thread */
                        System.Threading.ThreadPriority.BelowNormal
                    ,
                        /* The time limit for the thread (seven seconds) */
                        new System.TimeSpan ( 0 , 0 , 7 )
                    ,
                        /* The queue */
                        queue
                    ,
                        /* Pick a number from 10 to 100 */
                        (new System.Random()).Next ( 10 , 100 )
                    ) ;
                    /* Because the time is limited to seven seconds     */
                    /* and the thread will pause for a 
                       tenth of a second between iterations */
                    /* any thread that is instructed to iterate 
                       seventy or more times is    */
                    /* likely to run over the limit and may get killed  */
                }
                
                /* Step four: Run the threads */
                queue.Run ( threads ) ;
            }
            catch ( System.Exception err )
            {
                System.Console.Write ( err.Message ) ;
            }

            return ( 0 ) ;
        }
    }
}

Handling events

Here is the event handler of ThreadQueueDemo from the demo. It is static in the example, because it is referenced from Main, which is static. All it does is write a message to the console.

C#
namespace ThreadQueueDemo
{
    public partial class
    ThreadQueueDemo
    {
        /* Because all I want to do with the EventArgs 
           is write the ToString() to the console  */
        /* I really only need to write one handler that 
           will handle all seven different events */
        /* (Well I'll treat ThreadMessageEventArgs differently)*/
        public static void
        HandleThreadEvent
        (
            object   sender
        ,
            PIEBALD.Types.Event.ThreadEventArgs e
        )
        {
            if ( e is PIEBALD.Types.Event.ThreadMessageEventArgs )
            {
                System.Console.Write ( e.Thread.Name ) ;
            }
            else
            {
                System.Console.Write ( "<" + e.ToString() + ">" ) ;
            }
            
            return ;
        }
    }
}

Building and running the demo

Here I've extracted the files to their own directory, built the demo with the C# compiler that comes with the .NET 2.0 SDK, and executed the demo.

C:\Projects\CodeProject\Temp>dir
 Volume in drive C has no label.
 Volume Serial Number is 50B2-B107

 Directory of C:\Projects\CodeProject\Temp

2006-12-01  08:25    <DIR>          .
2006-12-01  08:25    <DIR>          ..
2006-11-22  09:59             2,102 LogFileIgnoreAttribute.cs
2006-11-21  11:51             2,325 ThreadAbortedEventArgs.cs
2006-11-21  11:51             2,330 ThreadAbortingEventArgs.cs
2006-11-21  11:51             2,331 ThreadCompleteEventArgs.cs
2006-11-21  11:42             4,157 ThreadEventArgs.cs
2006-11-21  11:51             3,396 ThreadMessageEventArgs.cs
2006-11-21  11:51             2,332 ThreadOverdueEventArgs.cs
2006-11-28  11:13            17,898 ThreadQueue.cs
2006-12-01  08:20             7,287 ThreadQueueDemo.cs
2006-11-22  08:13            14,313 ThreadQueueItem.cs
2006-11-21  11:51             2,329 ThreadStartingEventArgs.cs
2006-11-20  13:35             2,635 ThreadStatus.cs
2006-11-21  11:51             2,332 ThreadWaitingEventArgs.cs
              13 File(s)         65,767 bytes
               2 Dir(s)  165,827,227,648 bytes free

C:\Projects\CodeProject\Temp>csc *.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.


C:\Projects\CodeProject\Temp>ThreadQueueDemo
<Thread A is waiting to start at 2006-12-01 08:49:51>
<Thread B is waiting to start at 2006-12-01 08:49:51>
<Thread C is waiting to start at 2006-12-01 08:49:51>
<Thread D is waiting
to start at 2006-12-01 08:49:51>
<Thread E is waiting to start at 2006-12-01 08:49:51>
<Thread F is waiting to start at 2006-12-01 08:49:51>
<Thread G is waiting to start at 2006-12-0
1 08:49:51><Thread H is waiting to start at 2006-12-01 08:49:51>
<Thread I is waiting to start at 2006-12-01 08:49:51>
<Thread J is waiting to start at 2006-12-01 08:49:51><Thread K
is waiting to start at 2006-12-01 08:49:51>
<Thread L is waiting to start at 2006-12-01 08:49:51>
<Thread M is waiting to start at 2006-12-01 08:49:51>
<Thread N is waiting to start at 2006-12-01 08:49:51>
<Thread O is waiting to start at 2006-12-01 08:49:51>
<Thread P is waiting to start at 2006-12-01 08:49:51>
<Thread Q is waiting to start at 2006-12-01 08:49:51
><Thread R is waiting to start at 2006-12-01 08:49:51>
<Thread S is waiting to start at 2006-12-01 08:49:51>
<Thread T is waiting to start at 2006-12-01 08:49:51>
<Thread U is waiting to start at 2006-12-01 08:49:51>
<Thread V is waiting to start at 2006-12-01 08:49:51>
<Thread W is waiting to start at 2006-12-01 08:49:51>
<Thread X is waiting to start at 2006-12-01 08:49:51>
<Thread Y is waiting to start at 2006-12-01 08:49:51>
<Thread Z is waiting to start at 2006-12-01 08:49:51>
<Thread A is starting at 2006-12-01 08:49:51>
<Thread B is starting at 2006-12-01 08:49:51>AB
<Thread C is starting at 2006-12-01 08:49:51>
<Thread D is starting at 2006-12-01 08:49:51>C
<Thread E is starting at 2006-12-01 08:49:51>DEACDBEDABCEAD
BCEABCDEABCDEABDCEBCDAEABDCEACBDEACBDEABDCEADCBEBDACECBDAE
<Thread A has completed at 2006-12-01 08:49:53>
<Thread F is starting at 2006-12-01 08:49:53>
<Thread B has completed at 2006-12-01 08:49:53>
<Thread G is starting at 2006-12-01 08:49:53>F
<Thread C has completed at 2006-12-01 08:49:53>G
<Thread H is starting at 2006-12-01 08:49:53>
<Thread D has completed at 2006-12-01 08:49:53>
<Thread I is starting at 2006-12-01 08:49:53>H
<Thread E has completed at 2006-12-01 08:49:53>I
<Thread J is starting at 2006-12-01 08:49:53>JGIHJFGIJFHGIFHJGF
IHJGIHJFGHIJFHGIFJJHIGFIGHJFFHGJIIJGFHIJGHFIFGHJIFGHJ
<Thread F has completed at 2006-12-01 08:49:55>
<Thread K is starting at 2006-12-01 08:49:55>K
<Thread G has completed at 2006-12-01 08:49:55>
<Thread L is starting at 2006-12-01 08:49:55>
<Thread H has completed at 2006-12-01 08:49:55>
<Thread M is starting at 2006-12-01 08:49:55>LM
<Thread I has completed at 2006-12-01 08:49:55>
<Thread N is starting at 2006-12-01 08:49:55>
<Thread J has completed at 2006-12-01 08:49:55>
<Thread O is starting at 2006-12-01 08:49:55>
NOMKONLMNOKLMLNKOMKLONMOLNMKLONMKONLLKMONKNMOLKLNMOKNOMLNOMKLOMKLNOMKLN
<Thread K has completed at 2006-12-01 08:49:57>
<Thread P is starting at 2006-12-01 08:49:57>
<Thread L has completed at 2006-12-01 08:49:57>
<Thread Q is starting at 2006-12-01 08:49:57>QP
<Thread M has completed at 2006-12-01 08:49:57>
<Thread R is starting at 2006-12-01 08:49:57>
<Thread N has completed at 2006-12-01 08:49:57>R
<Thread S is starting at 2006-12-01 08:49:57>
<Thread O has completed at 2006-12-01 08:49:57>S
<Thread T is starting at 2006-12-01 08:49:57>
TSTPQRPSQTRPSQRTPSQRTPSQTRSQ
TRPPTSQRPSQTRPSQRTPSQTRPSQTRPSQRTPTSQRPRSQT
<Thread P has completed at 2006-12-01 08:49:58>
<Thread U is starting at 2006-12-01 08:49:58>
<Thread Q has completed at 2006-12-01 08:49:58>
<Thread V is starting at 2006-12-01 08:49:58>VU
<Thread R has completed at 2006-12-01 08:49:58>
<Thread W is starting at 2006-12-01 08:49:58>
<Thread S has completed at 2006-12-01 08:49:58>
<Thread X is starting at 2006-12-01 08:49:58>X
<Thread T has completed at 2006-12-01 08:49:58>
<Thread Y is starting at 2006-12-01 08:49:58>
YWXWVUYYVUWXYXUWVXVYWUUYWVXXUYWVVU
YWXUYWXVVUYWXVUYWXVXUWYVXWYUVWYUXXWYUV
<Thread U has completed at 2006-12-01 08:50:00>
<Thread Z is starting at 2006-12-01 08:50:00>
<Thread V has completed at 2006-12-01 08:50:00>Z<T
hread W has completed at 2006-12-01 08:50:00>
<Thread X has completed at 2006-12-01 08:50:00>
<Thread Y has completed at 2006-12-01 08:50:00>
ZZZZZZZZZZZZZZ<Thread Z has completed at 2006-12-01 08:50:02>
C:\Projects\CodeProject\Temp>

Points of Interest

I hope this at least serves as a decent intro to threading and events.

History

  • First submitted - 2006-12-01.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
BSCS 1992 Wentworth Institute of Technology

Originally from the Boston (MA) area. Lived in SoCal for a while. Now in the Phoenix (AZ) area.

OpenVMS enthusiast, ISO 8601 evangelist, photographer, opinionated SOB, acknowledged pedant and contrarian

---------------

"I would be looking for better tekkies, too. Yours are broken." -- Paul Pedant

"Using fewer technologies is better than using more." -- Rico Mariani

"Good code is its own best documentation. As you’re about to add a comment, ask yourself, ‘How can I improve the code so that this comment isn’t needed?’" -- Steve McConnell

"Every time you write a comment, you should grimace and feel the failure of your ability of expression." -- Unknown

"If you need help knowing what to think, let me know and I'll tell you." -- Jeffrey Snover [MSFT]

"Typing is no substitute for thinking." -- R.W. Hamming

"I find it appalling that you can become a programmer with less training than it takes to become a plumber." -- Bjarne Stroustrup

ZagNut’s Law: Arrogance is inversely proportional to ability.

"Well blow me sideways with a plastic marionette. I've just learned something new - and if I could award you a 100 for that post I would. Way to go you keyboard lovegod you." -- Pete O'Hanlon

"linq'ish" sounds like "inept" in German -- Andreas Gieriet

"Things would be different if I ran the zoo." -- Dr. Seuss

"Wrong is evil, and it must be defeated." –- Jeff Ello

"A good designer must rely on experience, on precise, logical thinking, and on pedantic exactness." -- Nigel Shaw

“It’s always easier to do it the hard way.” -- Blackhart

“If Unix wasn’t so bad that you can’t give it away, Bill Gates would never have succeeded in selling Windows.” -- Blackhart

"Use vertical and horizontal whitespace generously. Generally, all binary operators except '.' and '->' should be separated from their operands by blanks."

"Omit needless local variables." -- Strunk... had he taught programming

Comments and Discussions

 
Generaldr dobbs Pin
puromtec123-Sep-09 10:29
puromtec123-Sep-09 10:29 
Generalinput parameters Pin
Oseke Cilrh20-May-07 21:33
Oseke Cilrh20-May-07 21:33 
GeneralRe: input parameters Pin
PIEBALDconsult21-May-07 13:11
mvePIEBALDconsult21-May-07 13:11 
GeneralRe: input parameters Pin
Oseke Cilrh24-May-07 1:23
Oseke Cilrh24-May-07 1:23 
GeneralFormatting Pin
bd0ppens15-Dec-06 2:24
bd0ppens15-Dec-06 2:24 
GeneralRe: Formatting Pin
PIEBALDconsult18-Dec-06 6:15
mvePIEBALDconsult18-Dec-06 6:15 
GeneralRe: Formatting Pin
#realJSOP13-Nov-08 6:19
mve#realJSOP13-Nov-08 6:19 
GeneralRe: Formatting Pin
PIEBALDconsult13-Nov-08 12:48
mvePIEBALDconsult13-Nov-08 12:48 
GeneralThreads don't timeout Pin
craig.w7-Dec-06 12:55
craig.w7-Dec-06 12:55 
GeneralRe: Threads don't timeout Pin
PIEBALDconsult7-Dec-06 16:30
mvePIEBALDconsult7-Dec-06 16:30 

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.