It's old school.
I only recently found out that there is a slick way to do this in .Net.
First, you'll need a way to know when the thread has finished. Since there will be n of them, you'll need some type of collection.
Specifically, this will need to be a collection of ManualResetEvent objects.
For example,
System.Collections.Generic.List al = new List();
each time a new thread is needed, you'll need to create a new ManualResetEvent object, like this:
ManualResetEvent m = new ManualResetEvent(false);
(the boolean indicates whether to set the initial state).
Then you'll need to add this to the list:
al.Add(m);
ok, now that some of the thread junk is out of the way, we'll want to actually *do* something.
For example, lets stay we create a class called threadClass.
threadplay.threadClass c = new threadplay.threadClass(m);
in this example, i'm passing the ManualResetEvent to the constructor. The object will need a reference to it somewhere.
Next, you'll need a callback method. This will be what the thread calls to do it's work. It's the "DoStuff" method.
In my class, I called it "theCallback". It will need to have this signature:
public void theCallback(Object x)
Inside the callback method, you can typecast the Object to whatever makes sense. So you can pass in data connections, collections, or just about anything you like.
In my case, I created a class called "playObj" with some boring properties which i passed into the constructor:
new threadplay.playObj("kjh", i)
To set up this to run from a thread, just pop it onto the ThreadPool:
ThreadPool.QueueUserWorkItem(c.theCallback, new threadplay.playObj("kjh", i));
This will enqueue the thread to be run at the next possible moment. By default .Net will manage this thread pool for you. You can see or change the max or min number of threads if you want to, but .Net is suppose to correctly scale this based on system resources. If you're contenting with a resource .Net doesnt' know about (for example a hard limit on number of open database connections), you may want to modify the default behavior. But it is suppose to account for CPU utilization and be optimized already.
Inside the callback method, you'll want to call the Set() on the ManualResetEvent. Do it at the bottom of the method, when the execution of the method is done.
Generally, the call to QueueUserWorkItem will happen in a loop (otherwise, what's the point?).
The purpose of the ManualResetEvent is to allow you to wait for each thread to finish. Since you're not controlling the thread pool, you can't get to the actual threads easily, so you can't do a join(). Rather, you can do use WaitHandle.WaitAll() to wait for all the threads to finish (or WaitAny(), if you only want the first one).
Note that WaitAll() expects and Array, not a List<>. But you can convert.
WaitHandle.WaitAll(al.ToArray());
(for some types of data collections, you may need to typecast this to (ManualResetEvent[]). But since i was using a collection of type List, .Net does this for me).
That's it. The hardest part is the ManualReset event stuff.
This will create a pool of n-threads, enqueue any added after the nth until they can run, and wait for all to finish before continuing. Wiring it together is a bit of a pain because of the ManualResetEvent complexity, but besides that, it's very clean and removes the overhead of dealing with management of system resources.
--kevin