The method below is suppose to run for the (duration is milliseconds) being passed in for case 0:, but what I'm seeing is the method may take up to 2 seconds to run for a 400ms duration. Is it possible that Task.run is taking long time to start? If so is there a better way?
private static async void PulseWait(int duration, int axis){
await Task.Run(() =>
{
try
{
var logaction = true;
switch (axis)
{
case 0:
var sw1 = Stopwatch.StartNew();
if (duration > 0) duration += 20; // allowance for the call to the mount
while (sw1.Elapsed.TotalMilliseconds <= duration) { } // wait out the duration
_isPulseGuidingRa = false;
logaction = false;
break;
case 1:
var axis2Stopped = false;
var loopcount = 0;
switch (SkySettings.Mount)
{
case MountType.Simulator:
while (!axis2Stopped && loopcount < 30)
{
loopcount++;
var statusy = new CmdAxisStatus(MountQueue.NewId, Axis.Axis2);
var axis2Status = (AxisStatus)MountQueue.GetCommandResult(statusy).Result;
axis2Stopped = axis2Status.Stopped;
if (!axis2Stopped) Thread.Sleep(10);
}
break;
case MountType.SkyWatcher:
while (!axis2Stopped && loopcount < 30)
{
loopcount++;
var statusy = new SkyIsAxisFullStop(SkyQueue.NewId, AxisId.Axis2);
axis2Stopped = Convert.ToBoolean(SkyQueue.GetCommandResult(statusy).Result);
if (!axis2Stopped) Thread.Sleep(10);
}
break;
default:
throw new ArgumentOutOfRangeException();
}
_isPulseGuidingDec = false;
logaction = false;
break;
}
var monitorItem = new MonitorEntry
{ Datetime = HiResDateTime.UtcNow, Device = MonitorDevice.Telescope, Category = MonitorCategory.Mount, Type = MonitorType.Data, Method = MethodBase.GetCurrentMethod().Name, Thread = Thread.CurrentThread.ManagedThreadId, Message = $"PulseGuide={logaction}" };
MonitorLog.LogToMonitor(monitorItem);
}
catch (Exception)
{
_isPulseGuidingDec = false;
_isPulseGuidingRa = false;
}
});}
Log showing how time taken...
33652,2019:07:12:01:15:35.590,13,AxisPulse,Axis1,0.00208903710815278,400,0,True <<--line just before PulseWait is called with 400ms duration
33653,2019:07:12:01:15:35.591,13,SendRequest,:I1250100
33654,2019:07:12:01:15:35.610,13,ReceiveResponse,:I1250100,=
33655,2019:07:12:01:15:36.026,13,SendRequest,:I1B70100
33656,2019:07:12:01:15:36.067,13,ReceiveResponse,:I1B70100,=
33657,2019:07:12:01:15:36.067,13,SendRequest,:j1
33658,2019:07:12:01:15:36.120,13,ReceiveResponse,:j1,=DDCDBD
33659,2019:07:12:01:15:36.120,13,SendRequest,:j2
33660,2019:07:12:01:15:36.165,13,ReceiveResponse,:j2,=67CF8A
33661,2019:07:12:01:15:36.467,13,SendRequest,:j1
33662,2019:07:12:01:15:36.484,13,ReceiveResponse,:j1,=10CEBD
33663,2019:07:12:01:15:36.484,13,SendRequest,:j2
33664,2019:07:12:01:15:36.501,13,ReceiveResponse,:j2,=67CF8A
33665,2019:07:12:01:15:36.808,13,SendRequest,:j1
33666,2019:07:12:01:15:36.842,13,ReceiveResponse,:j1,=3CCEBD
33667,2019:07:12:01:15:36.842,13,SendRequest,:j2
33668,2019:07:12:01:15:36.868,13,ReceiveResponse,:j2,=67CF8A
33669,2019:07:12:01:15:37.170,13,SendRequest,:j1
33670,2019:07:12:01:15:37.188,13,ReceiveResponse,:j1,=6BCEBD
33671,2019:07:12:01:15:37.188,13,SendRequest,:j2
33672,2019:07:12:01:15:37.204,13,ReceiveResponse,:j2,=67CF8A
33673,2019:07:12:01:15:37.221,5,b__0,PulseGuide=False <<--PulseWait is finished 1.631ms after start
The purpose of async and await is to make things easy. But just like everything that makes things easy, it comes with a cost of having full control over what's going on. Here, it's really a cost of asynchronous programming in general. The point of asynchronous programming is to free up the current thread so that the current thread can go off and do something else. But if something else is done on the current thread, then the continuation of what you were doing must wait until that is done. (i.e. What comes after the await may not happen instantaneously after the task completes)
So while asynchronous programming will help overall performance (like increasing the overall throughput performance of a web app), but will actually hurt the performance of any one specific task. If every millisecond counts to you, you might be able to do the low-level tasks yourself, like creating a Thread (if this really needs to be run on a separate thread).
Here is a simple example that demonstrates this:
var s = new Stopwatch();
// Test the time it takes to run an empty method on a
// different thread with Task.Run and await it.
s.Start();
await Task.Run(() => { });
s.Stop();
Console.WriteLine($"Time of Task.Run: {s.ElapsedMilliseconds}ms");
// Test the time it takes to create a new thread directly
// and wait for it.
s.Restart();
var t = new Thread(() => { });
t.Start();
t.Join();
s.Stop();
Console.WriteLine($"Time of new Thread: {s.ElapsedMilliseconds}ms");
The output will vary, but it looks something like this:
Time of Task.Run: 8ms
Time of new Thread: 0ms
In an application with lots of other things going on, that 8ms could be much more if some other operation uses the thread during the await.
That's not to say that you should use Thread. t.Join() is not an asynchronous operation. It will block the thread. So if PulseWait runs on the UI thread (if this is a UI app), it will lock the UI thread, which is a bad user experience. In that case, you may not be able to get around the cost of using asynchronous code.
If this is not an application with a UI, then I don't see why you need to do all that on a different thread at all. Maybe you can just.... not do that.
Related
var finalList = new List<string>();
var list = new List<int> {1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ................. 999999};
var init = 0;
var limitPerThread = 5;
var countDownEvent = new CountdownEvent(list.Count);
for (var i = 0; i < list.Count; i++)
{
var listToFilter = list.Skip(init).Take(limitPerThread).ToList();
new Thread(delegate()
{
Foo(listToFilter);
countDownEvent.Signal();
}).Start();
init += limitPerThread;
}
//wait all to finish
countDownEvent.Wait();
private static void Foo(List<int> listToFilter)
{
var listDone = Boo(listToFilter);
lock (Object)
{
finalList.AddRange(listDone);
}
}
This doesn't:
var taskList = new List<Task>();
for (var i = 0; i < list.Count; i++)
{
var listToFilter = list.Skip(init).Take(limitPerThread).ToList();
var task = Task.Factory.StartNew(() => Foo(listToFilter));
taskList.add(task);
init += limitPerThread;
}
//wait all to finish
Task.WaitAll(taskList.ToArray());
This process must create at least 700 threads in the end. When I run using Thread, it works and creates all of them. But with Task it doesn't.. It seems like its not starting multiples Tasks async.
I really wanna know why.... any ideas?
EDIT
Another version with PLINQ (as suggested).
var taskList = new List<Task>(list.Count);
Parallel.ForEach(taskList, t =>
{
var listToFilter = list.Skip(init).Take(limitPerThread).ToList();
Foo(listToFilter);
init += limitPerThread;
t.Start();
});
Task.WaitAll(taskList.ToArray());
EDIT2:
public static List<Communication> Foo(List<Dispositive> listToPing)
{
var listResult = new List<Communication>();
foreach (var item in listToPing)
{
var listIps = item.listIps;
var communication = new Communication
{
IdDispositive = item.Id
};
try
{
for (var i = 0; i < listIps.Count(); i++)
{
var oPing = new Ping().Send(listIps.ElementAt(i).IpAddress, 10000);
if (oPing != null)
{
if (oPing.Status.Equals(IPStatus.TimedOut) && listIps.Count() > i+1)
continue;
if (oPing.Status.Equals(IPStatus.TimedOut))
{
communication.Result = "NOK";
break;
}
communication.Result = oPing.Status.Equals(IPStatus.Success) ? "OK" : "NOK";
break;
}
if (listIps.Count() > i+1)
continue;
communication.Result = "NOK";
break;
}
}
catch
{
communication.Result = "NOK";
}
finally
{
listResult.Add(communication);
}
}
return listResult;
}
Tasks are NOT multithreading. They can be used for that, but mostly they're actually used for the opposite - multiplexing on a single thread.
To use tasks for multithreading, I suggest using Parallel LINQ. It has many optimizations in it already, such as intelligent partitioning of your lists and only spawning as many threads as there ar CPU cores, etc.
To understand Task and async, think of it this way - a typical workload often includes IO that needs to be waited upon. Maybe you read a file, or query a webservice, or access a database, or whatever. The point is - your thread gets to wait a loooong time (in CPU cycles at least) until you get a response from some faraway destination.
In the Olden Days™ that meant that your thread was getting locked down (suspended) until that response came. If you wanted to do something else in the meantime, you needed to spawn a new thread. That's doable, but not too efficient. Each OS thread carries a significant overhead (memory, kernel resources) with it. And you could end up with several threads actively burning the CPU, which means that the OS needs to switch between them so that each gets a bit of CPU time and these "context switches" are pretty expensive.
async changes that workflow. Now you can have multiple workloads executing on the same thread. While one piece of work is awaiting the result from a faraway source, another can step in and use that thread to do something else useful. When that second workload gets to its own await, the first can awaken and continue.
After all, it doesn't make sense to spawn more threads than there are CPU cores. You're not going to get more work done that way. Just the opposite - more time will be spent on switching the threads and less time will be available for useful work.
That is what the Task/async/await was originally designed for. However Parallel LINQ has also taken advantage of it and reused it for multithreading. In this case you can look at it this way - the other threads is what your main thread is the "faraway destination" that your main thread is waiting on.
Tasks are executed on the Thread Pool. This means that a handful of threads will serve a large number of tasks. You have multi-threading, but not a thread for every task spawned.
You should use tasks. You should aim to use as much threads as your CPU. Generally, the thread pool is doing this for you.
How did you measure up the performance? Do you think that the 700 threads will work faster than 700 tasks executing by 4 threads? No, they would not.
It seems like its not starting multiples Tasks async
How did you came up with this? As other suggested in comments and in other answers, you probably need to remove a thread creation, as after creating 700 threads you'll degrade your system performance, as your threads would fight to each other for the processor time, without any work done faster.
So, you need to add the async/await for your IO operations, into the Foo method, with SendPingAsync version. Also, your method could be simplyfied, as many checks for a listIps.Count() > i + 1 conditions are useless - you do it in the for condition block:
public static async Task<List<Communication>> Foo(List<Dispositive> listToPing)
{
var listResult = new List<Communication>();
foreach (var item in listToPing)
{
var listIps = item.listIps;
var communication = new Communication
{
IdDispositive = item.Id
};
try
{
var ping = new Ping();
communication.Result = "NOK";
for (var i = 0; i < listIps.Count(); i++)
{
var oPing = await ping.SendPingAsync(listIps.ElementAt(i).IpAddress, 10000);
if (oPing != null)
{
if (oPing.Status.Equals(IPStatus.Success)
{
communication.Result = "OK";
break;
}
}
}
}
catch
{
communication.Result = "NOK";
}
finally
{
listResult.Add(communication);
}
}
return listResult;
}
Other problem with your code is that PLINQ version isn't threadsafe:
init += limitPerThread;
This can fail while executing in parallel. You may introduce some helper method, like in this answer:
private async Task<List<PingReply>> PingAsync(List<Communication> theListOfIPs)
{
Ping pingSender = new Ping();
var tasks = theListOfIPs.Select(ip => pingSender.SendPingAsync(ip, 10000));
var results = await Task.WhenAll(tasks);
return results.ToList();
}
And do this kind of check (try/catch logic removed for simplicity):
public static async Task<List<Communication>> Foo(List<Dispositive> listToPing)
{
var listResult = new List<Communication>();
foreach (var item in listToPing)
{
var listIps = item.listIps;
var communication = new Communication
{
IdDispositive = item.Id
};
var check = await PingAsync(listIps);
communication.Result = check.Any(p => p.Status.Equals(IPStatus.Success)) ? "OK" : "NOK";
}
}
And you probably should use Task.Run instead of Task.StartNew for being sure that you aren't blocking the UI thread.
THERE'S AN UPDATE BELOW THIS INITIAL QUESTION
I have a query that pulls in about 90,000 header records. I then want to iterate through that result set to get detail data for each header retrieved. If I process it linearly it take close to an hour and a half. If I parallelize it, I can get it done in 11 minutes. However, there's no screen updates. I have done multi-threaded applications lots of times, and have always been successful with doing things like:
this.lblStatus.Invoke(new MethodInvoker(() => this.lblStatus.Text = "Updating Standards Docs"));
However, this appears to really screen up a Parallel loop. Using that method for some screen updates, the Parallel loop never actually finished. So I need another method.
I've been trying:
Task.Factory.StartNew(() =>
{
OrderablePartitioner<PayrollHeader> partitioner = Partitioner.Create(query, EnumerablePartitionerOptions.NoBuffering);
Parallel.ForEach(partitioner, thisCheck =>
{
Interlocked.Increment(ref iChckNo);
lock (_status)
{
_status.ProcessMsg = "Voucher " + thisCheck.VoucherNumber;
_status.ProcessName = thisCheck.EmployeeName;
_status.CurrentRec = iChckNo;
dtSpan = DateTime.Now.Subtract(dtSpanStart);
_status.TimeMsg = string.Format("Elapsed {0}:{1}:{2}", dtSpan.Hours, dtSpan.Minutes, dtSpan.Seconds);
}
BeginInvoke((Action) (() =>
{
lblVoucher.Text = _status.ProcessMsg;
lblName.Text = _status.ProcessName;
lblCount.Text = string.Format("Record {0} of {1}", _status.CurrentRec, _status.TotalRecs);
lblTime.Text = _status.TimeMsg;
Application.DoEvents();
}));
thisCheck.GetDetails();
});
}).Wait();
The wait on the Task is because afterwards I need to do something else with the query afterwards, which I'll put into a ContinueWith statement eventually, I just really need to get the screen update to work.
I know all about cross thread corruption, which is why I'm trying to use the Invoker method... I firmly believe long running processes still need to keep the user informed, which is why I'm attempting this.
BTW, it's a WinForms app, not a WPF app. Any help at all would be greatly appreciated...
UPDATE:So someone wanted to see the updated code, with the IProgress into it.
Status _status = new Status {TotalRecs = query.Count};
var progress = new Progress<Status>(msg => WriteStatusUpdate(msg));
Task.Run(() =>
{
OrderablePartitioner<PayrollHeader> partitioner = Partitioner.Create(query, EnumerablePartitionerOptions.NoBuffering);
Parallel.ForEach(partitioner, thisCheck =>
{
lock (_status)
{
_status.ProcessMsg = "Voucher " + thisCheck.VoucherNumber;
_status.ProcessName = thisCheck.EmployeeName;
_status.CurrentRec = ++iChckNo;
dtSpan = DateTime.Now.Subtract(dtSpanStart);
_status.TimeMsg = string.Format("Elapsed {0}:{1}:{2}", dtSpan.Hours, dtSpan.Minutes, dtSpan.Seconds);
}
((IProgress<Status>) progress).Report(_status);
thisCheck.GetDetails();
});
}).Wait();
private void WriteStatusUpdate(Status _status)
{
lblVoucher.Text = _status.ProcessMsg;
lblVoucher.Refresh();
lblName.Text = _status.ProcessName;
lblName.Refresh();
lblCount.Text = string.Format("Records {0} of {1}", _status.CurrentRec, _status.TotalRecs);
lblCount.Refresh();
lblTime.Text = _status.TimeMsg;
lblTime.Refresh();
}
The code to update the screen never gets called...
Don't try to update the UI from inside the parallel loop. It's not just that you can't update the UI from inside a background thread, it results in ugly and unmaintainable code. The parallel loop should do processing. Reporting should be performed by someone else.
The .NET Framework provides the IProgress< T> interface to report progress and the default implementation Progress< T> raises an event or calls a delegate on its creator thread, eg the UI thread. This results in much simpler code, eg:
var stopwatch = Stopwatch.StartNew();
var progressImpl=new Progress<Tuple<int,string,string>>(
msg=>ReportProgress(msg,stopwatch))
IProgress<Tuple<int,string,string>> progress=progressImpl;
var partitioner = Partitioner.Create(query, EnumerablePartitionerOptions.NoBuffering);
Task.Run(()=> Parallel.ForEach(partitioner, thisCheck =>
{
....
var msg=Tuple.Create(iChckNo,thisCheck.VoucherNumber,thisCheck.EmployeeName);
progress.Report(msg);
...
})
);
...
private void ReportProgress(Tuple<int,string,string> msg,Stopwatch stopwatch)
{
_status.ProcessMsg = "Voucher " + msg.Item2;
_status.ProcessName = msg.Item3;
_status.CurrentRec = msg.Item1;
_status.TimeMsg = string.Format("Elapsed {0:c}", stopwatch.Elapsed);
};
I'm being very lazy here by using a Tuple<int,string,string> instead of a more specific class.
Messages sent from inside the parallel loop will be marshaled on the UI thread by Progress<T> and the ReportProgress function will be called on the UI thread itself.
The cast to IProgress< T> is necessary because the Publish method is explicitly implemented. This is a safety measure to prevent programmers from coding against the implementation itself.
I couldn't get the IProgress thing to actually work, so what I ended up doing, which is probably not the best approach is I put the Parallel.ForEach in a Task, and in the loop I update a public object. When the Task actually starts I sit in a while loop until it's done, and in that while loop I'm updating the UI...
bool blDone = false;
int iChckNo = 0;
_status.TotalRecs = query.Count;
Task.Run(() =>
{
OrderablePartitioner<PayrollHeader> partitioner = Partitioner.Create(query, EnumerablePartitionerOptions.NoBuffering);
Parallel.ForEach(partitioner, thisCheck =>
{
lock (_status)
{
iChckNo++;
_status.ProcessMsg = "Voucher " + thisCheck.VoucherNumber;
_status.ProcessName = thisCheck.EmployeeName;
_status.CurrentRec = iChckNo;
dtSpan = DateTime.Now.Subtract(dtSpanStart);
_status.TimeMsg = string.Format("Elapsed {0}:{1}:{2}", dtSpan.Hours, dtSpan.Minutes, dtSpan.Seconds);
}
thisCheck.GetDetails();
});
blDone = true;
});
while (!blDone)
{
WriteStatusUpdate();
}
further down in the code is
private void WriteStatusUpdate()
{
lock (_status)
{
lblVoucher.Text = _status.ProcessMsg;
lblName.Text = _status.ProcessName;
lblCount.Text = string.Format("Records {0} of {1}", _status.CurrentRec, _status.TotalRecs);
lblTime.Text = _status.TimeMsg;
Application.DoEvents();
}
}
Again, most likely not the best approach, but whatever gets it done...
I have such particular code:
for (int i = 0; i < SingleR_mustBeWorkedUp._number_of_Requestes; i++)
{
Random myRnd = new Random(SingleR_mustBeWorkedUp._num_path);
while (true)
{
int k = myRnd.Next(start, end);
if (CanRequestBePutted(timeLineR, k, SingleR_mustBeWorkedUp._time_service, start + end) == true)
{
SingleR_mustBeWorkedUp.placement[i] = k;
break;
}
}
}
I use an infinite loop here which will end only if CanRequestBePutted returns true. So how to know that the app isn't responding?
There is a solution by controlling time of working each loop, but it doesn't seem to be really good. And I can't forecast that is going to happen in every cases.
Any solutions?
If you're concerned that this operation could potentially take long enough for the application's user to notice, you should be running it in a non-UI thread. Then you can be sure that it will not be making your application unrepsonsive. You should only be running it in the UI thread if you're sure it will always complete very quickly. When in doubt, go to a non-UI thread.
Don't try to figure out dynamically whether the operation will take a long time or not. If it taking a while is a possibility, do the work in another thread.
Why not use a task or threadpool so you're not blocking and put a timer on it?
The task could look something like this:
//put a class level variable
static object _padlock = new object();
var tasks = new List<Task>();
for (int i = 0; i < SingleR_mustBeWorkedUp._number_of_Requestes; i++)
{
var task = new Task(() =>
{
Random myRnd = new Random(SingleR_mustBeWorkedUp._num_path);
while (true)
{
int k = myRnd.Next(start, end);
if (CanRequestBePutted(timeLineR, k, SingleR_mustBeWorkedUp._time_service, start + end) == true)
{
lock(_padlock)
SingleR_mustBeWorkedUp.placement[i] = k;
break;
}
}
});
task.Start();
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
However I would also try to figure out a way to take out your while(true), which is a bit dangerous. Also Task requires .NET 4.0 or above and i'm not sure what framework your targeting.
If you need something older you can use ThreadPool.
Also you might want to put locks around shared resources like SingleR_mustBeWorkedUp.placement or anywhere else might be changing a variable. I put one around SingleR_mustBeWorkedUp.placement as an example.
I have a method that occasionally hangs (in a dll I cannot modify but must use). If I run it again It will typically work fine. I was wondering if it would be possible to make a background thread that would wait for 20 minutes and then throw an exception in my program.
var triesLeft = 5;
while (triesLeft > 0) {
try {
var t = new Thread(() => { wait(20 minutes); throw new ApplicationHungException();})
t.Start();
Object o = MethodThatHangsForever10PercentOfTheTime();
} catch (ApplicationHungException e) {
triesLeft--;
}
}
t.Abort();
This does not work because the exception does not pass to the try catch block it's contained in. Is there a way I can get the thread to give it's exception to the try catch block?
One way to do this would be set off your faulty method in the separate thread, and wait for one of two things to happen; either:
The thread completes, or
A predetermined amount of time (eg 20 mins) elapses
Once either of these things happens, we can take appropriate action.
The code would look something like this:
static void DoProcessing() {
var triesLeft = 5;
Object o = null;
while (triesLeft > 0) {
var t = new Thread(() => { o = MethodThatHangsForever10%OfTheTime(); }).Start();
if (t.Join(new TimeSpan(0, 20, 0))) {
// The thread completed
break;
} else {
// We're out of time.
t.Abort(); // Important: See comments below about this
triesLeft--;
}
}
}
It turns out that aborting threads is a risky and fragile operation, as pointed out by Reed Copsey in the comments below. Your alternatives are to allow the hung thread to live out the rest of its life (however long that may be), or to quarantine the Heisenbuggy method call to a separate process.
This opens up another can of worms however, since you would have to deal with interprocess communication, data serialization and synchronisation. This may or may not be worth it, which is a judgement call I leave to you.
You can do your work in a separate thread, and wait 20 minutes for it to complete:
var triesLeft = 5;
while (triesLeft > 0)
{
var mre = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(_ => {
MethodThatHangsForever10PercentOfTheTime();
mre.Set();
});
if (mre.WaitOne(TimeSpan.FromMinutes(20)))
{
break; // Success!
}
triesLeft--;
}
}
I've searched all morning and I can't seem to find the answer to this question.
I have an array of Threads each doing work and then I'll loop through the ids joining each one then starting new threads. What's the best way to detect when a thread has finish so I can fire off a new thread without waiting for each thread to finish?
EDIT added code snippet maybe this will help
if (threadCount > maxItems)
{
threadCount = maxItems;
}
threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++)
{
threads[i] = new Thread(delegate() { this.StartThread(); });
threads[i].Start();
}
while (loopCounter < threadCount)
{
if (loopCounter == (threadCount - 1))
{
loopCounter = 0;
}
if (threads[loopCounter].ThreadState == ThreadState.Stopped)
{
threads[loopCounter] = new Thread(delegate() { this.StartThread(); });
threads[loopCounter].Start();
}
}
Rather than creating new thread each time, why not just have each thread call a function that returns the next ID (or null if there's no more data to process) when it's finished with the current one? That function will obviously have to be threadsafe, but should reduce your overhead versus watching for finished threads and starting new ones.
so,
void RunWorkerThreads(int threadCount) {
for (int i = 0; i < threadCount; ++i) {
new Thread(() => {
while(true) {
var nextItem = GetNextItem();
if (nextItem == null) break;
/*do work*/
}
}).Start();
}
}
T GetNextItem() {
lock(_lockObject) {
//return the next item
}
}
I'd probably pull GetNextItem and "do work" out and pass them as a parameters to RunWorkerThreads to make that more generic -- so it would be RunWorkerThreads<T>(int count, Func<T> getNextItem, Action<T> workDoer), but that's up to you.
Note that Parallel.ForEach() does essentially this though plus give ways of monitoring and aborting and such, so there's probably no need to reinvent the wheel here.
You can check the thread's ThreadState property and when it's Stopped you can kick off a new thread.
http://msdn.microsoft.com/en-us/library/system.threading.thread.threadstate.aspx
http://msdn.microsoft.com/en-us/library/system.threading.threadstate.aspx
Get each thread, as the last thing it does, to signal that it is done. That way there needs to be no waiting at all.
Even better move to a higher level of abstraction, e.g. threadpool and let someone else worry about such details.