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






4.14/5 (9 votes)
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.