Click here to Skip to main content
Email Password   helpLost your password?

Introduction

Have you ever needed to run a whole lot of methods asynchronously? Did you need to limit the number running at once? How about having an interactive role in their control? Well, the NotifyingThreadQueue might be just for you.

Comparison

Microsoft is really good at giving us the tools to do things we need in many different ways, but sometimes it can be hard to tell what to use. In my previous article, I had a lot of comments on this problem. I blame my poor presenting. This section is an effort to fix that.

System.Threading.Thread

Pros

Cons

System.Threading.ThreadPool

This is the closest to what I have made.

Pros

Cons

System.Windows.Forms.BackgroundWorker

Pros

Cons

"Gang of Four" Delegate Pattern

I don't actually own this book so I can't vouch for its worth, but I hear it is good. This is here because the book I do have references their pattern as similar to this more well known pattern.

"Thilmany" Notifying Thread Manager

See references. I own this book. I like it.

Pros

Cons

NotifyingThreadQueue

Pros

Cons

Queue Operations

The NotifyingThreadQueue has several operations on it that will let you take a somewhat interactive approach to determining what the queue is doing.

Stop

This tells the NotifyingThreadQueue to not accept any more work and to delete all the work it has waiting but didn't start. It lets the threads it already started on complete their operation. It does not call Abort(). When you call this operation, the queue will call its event ThreadError. This is a special case of the event. The event contains the unprocessed, as well as the exception “System.Threading.ThreadStateException("the Queue is stopping. No processing done").” I did this to let you reclaim the unprocessed objects.

Continue

This tells the NotifyingThreadQueue to continue processing items in its queue. It will not continue once Stop() has been called.

Pause

This tells the NotifyingThreadQueue to continue to receive items to enqueue, but not to execute any more until Continue() is called. You cannot Pause() the Stop() command.

Queue Events

QueueStateChanged

I made the queue have state so I could add the queue Operations. I added this since it didn't really take that much effort.

ThreadFinished

When an operation is finished it would be nice if you were somehow informed. This does that.

ThreadError

The ThreadError event serves two purposes. The first is to let you know that there was an error in your operation. This is important for me to catch since it usually (always?) kills the thread and gives no error anywhere in VS. It also serves to tell you that the object was not processed because the queue was stopped. I think that’s a nice feature. This lets you reclaim the object if you really want to do something with it.

Things of Note

private void RunOpp(object o)
{
KeyValuePair<T, QueueOperationHandler<T>> kvp = 
	(KeyValuePair<T, QueueOperationHandler<T>>)o;
...
}

OMG. A cast in .NET 2.0! Hearsay, I say, Hearsay! Even in .NET 2.0, you don't have a method to start a thread that takes a strongly typed parameter. Even its new:

ParameterizedThreadStart(void(object) target). 

One might think that this could have been made into a generic, but apparently it wasn't. This isn't too bad since the method is private AND the value that gets put into it is always coming out of a strongly typed queue. So it isn't really that bad.

Gotchas

I made the NotifyingThreadQueue to be used as a middle-tier component. However, I use it as a presentation layer component of the demo. This means that there are cross threading issues. If you are unfamiliar with this, it is a .NET 2.0 thing. Microsoft decided to not let a thread access a System.Windows.Forms.Control created on a different thread. There are 2 solutions to this:

<yourcontrolname>.CheckForIllegalCrossThreadCalls = false; 

CheckForIllegalCrossThreadCalls is a static bool on all System.Windows.Forms.Controls. Basically, it says to ignore all of this type of errors and continue. This works, but if you get a LOT of these errors, then your application may actually slow down noticeably. If you have only a few of these, then this is a quick fix.
Note: Are you updating a progress bar? You will get one error per update.
Other instances of this error include setting the Text field of any of your controls. I get mine by calling my <listviews>.Items.Add();

Use a delegate and invoke the call on the thread that created the control.

private delegate void AddCallback(ListView lv, string value);
private void AddLVIC(ListView lv, string value)
{
lv.Items.Add(value);
}
this.Invoke(new AddCallback(AddLVIC), new object[] { lvStatus, qs.ToString() });

This is a better way to do things. It does, however, get tedious to do this if you have a lot of updates or a lot of different type of updates. This is the real disadvantage of my design. But like I said, I am going to use this somewhere else.

Silent cross-thread exception dropping. I recognize that you might have the cross-thread exception issue, so I decided to catch and silently drop it. This is a double edged sword. QueueStateChangedInternal, ThreadFinishedInternal, and ThreadErrorInternal are the culprits. If I find that I really don't like this, I might do another constructor. It just depends on my own use.

Demo Application

Sample screenshot

The demo application is nothing fancy. The 6 top buttons let you play with one or two operators and the max thread counts. The bottom 4 are for interactive viewing so you can see the start changed and that errors are caught and handled. You can also pause, stop, and continue the operation to see if you like the flow.

References

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralVery nice thanx a lot.
Bassam Alugili
3:48 7 Jan '09  
Very nice thanx a lot.
GeneralCongratulations!
vvvv66
4:42 1 Sep '08  
Very nice approach and vision. Wink
GeneralBetter sample ??
twesterd
0:05 27 Aug '07  
Your example does nothing on each thread. You pass in an integer and get a callback with that very same integer.

Please, take the time to actually create a sample where the thread actually invokes a method that does something. A better example would show how different methods could be added to the pool.

I wish I had more time to look at it (just glanced quickly and ran the demo)... it seems you spent more time explaining your poorly (or not so nicely) handled issue with the UI thread. There are much more elegant ways to handle your UI thread issue and your thread pool should not be changed or adapted for the UI at all. It was a bad decesion (IMO) to add cross-threading checks in the queue itself. The UI should have a manager that creates the queue and deals with invoke required. Having the queue tightly coupled to UI issues is the wrong approach. Now seeing what looks promising for a ThreadPool is wrapped in a tattered UI cloak. Then you don't go deep with the ThreadPool, you go deep with the UI issue. Arg!

I recommend that you explain what is "queue'd" (should be a method and should accept parameters) and that you show how different methods can be added to the queue.

Really, is it a very good example to show that a thread held your integer and returned the same integer? You example proves little in regard to the ThreadPool actually working when it doen't do anything on the separate threads. It would be nice to see that the threads actaully do something. Further, I think it will confuse many readers because they may not get what is actually run and how for each thread in the pool.
GeneralGreat Job! - Usage with many thread classes
5:43 22 Feb '07  
I have used this with many different thread classes - all with a common base class and it works well.Smile

Example:

private ThreadControlQueue<WorkThreadBase> workQueue;

workQueue = new ThreadControlQueue<WorkThreadBase>(ExecuteWork);
workQueue.ThreadFinished += new ThreadFinishedHandler<WorkThreadBase>(workQueue _ThreadFinished);
workQueue.ThreadError += new ThreadErrorHandler<WorkThreadBase>(workQueue _ThreadError);
workQueue.QueueStateChanged += new QueueStateChangedHandler(workQueue _QueueStateChanged);

// Now enqueue threads with the common base class
ExportTable1 thdTable1 = new ExportTable1(this.Database);
workQueue .EnQueue(thdTable1 , thdTable1.Export);

ExportTable2 thdTable2 = new ExportTable2(this.Database);
workQueue.EnQueue(thdTable2, thdTable2.Export);

I made one minor change in the TryNewThread() method after "#region Idle / Paused"
I added:
if (currentthreads != 0){

around
ThreadErrorInternal(default(T), new Exception...

This seemed to be needed due to the way I was adding after the Thread pool was created.


Stephen Popp
GeneralControl.Invoke is there for a reason...
Nate Kohari
11:20 14 Sep '06  
In reference to:

This means that there are cross threading issues. If you are unfamiliar with this, it is a .Net 2.0 thing. Microsoft decided to not let a thread access a System.Windows.Forms.Control created on a different thread.
This restriction isn't something that was introduced in .NET 2.0; it's been around Windows forever. It's so important to Windows development that it's sometimes referred to as the One Rule. In .NET 2.0, Microsoft added the ability to throw an exception when your application violates the One Rule.

The basic pattern is:

public void UpdateInterface()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(UpdateInterface));
return;
}

// Actually update the interface here
}

If you want to know the how's and why's, a great description is available here. (Also, for real fun, violate the One Rule on the Compact Framework 1.0... your app will actually freeze in its tracks! Smile
GeneralRe: Control.Invoke is there for a reason...
Mark Newman #2
11:38 14 Sep '06  
Perhaps, I should have said "changed in Net 2.0" as opposed to
"it is a .Net 2.0 thing". The 1.1 framework would let you violate the One Rule without fuss. I can't speak for the 1.0 compact as I have never used it. It is quite possible that the non compact 1.1 framework hid this from you, or out right ignored it. All I can say is that in 1.1 you didn’t have to have the call and in 2.0 you do need it.

GeneralThread exceptions in 2.0
megagreg
3:39 14 Aug '06  
You mentioned something about the System.Threading.Thread class and how pause/continue are deprecated in .NET 2.0, but neglect to mention that exceptions work differently as well. They no longer kill the thread silently as they did in previous versions. They now unwind the entire application. It's a little frustrating when the exceptions start working differently like this, but it's a better design.
GeneralThanks for the article, I will keep it in mind for future use
al13n
14:41 9 Aug '06  
Please note that there are a few minor spelling errors in the article and demo program:
"Stopping" (not stoping)
"Pausing" (not pauseing)
"Heresy" (not hearsay)
"does" (not dose)

Thanks again Smile


Last Updated 3 Aug 2006 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010