Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

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

0.00/5 (No votes)
6 Dec 2006 1  
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:

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.

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.

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.

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.

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 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