Reputation: 8628
I am very new to threading, as a beginner in C#. I have a program that will be firing multiple threads inside of a windows app. My aim is to start a new thread for every item within a list. The items in this list are workstation names on anetwork. Each thread that is created will look to do repairs on each machine, when the thread has finished it will write to a log file of any errors found etc. But what i want to be able to determine is when all threads have finished. So if i have 100 machines, 100 threads, how do i determine when all have closed?
Heres my method below :-
private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e)
{
if (machineList.Count() != 0)
{
foreach (string ws in machineList)
{
new Thread(new ParameterizedThreadStart(fixClient), stack).Start(ws);
}
}
else
{
MessageBox.Show("Please import data before attempting this procedure");
}
}
Upvotes: 8
Views: 26851
Reputation: 48949
Let us get something out of the way first.
Thread.Join
, WaitHandle.WaitOne
, or any other blocking mechanism.Here is how I would do this with the TPL.
private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e)
{
if (machineList.Count() != 0)
{
// Start the parent task.
var task = Task.Factory.StartNew(
() =>
{
foreach (string ws in machineList)
{
string capture = ws;
// Start a new child task and attach it to the parent.
Task.Factory.StartNew(
() =>
{
fixClient(capture);
}, TaskCreationOptions.AttachedToParent);
}
}, TaskCreationOptions.LongRunning);
// Define a continuation that happens after everything is done.
task.ContinueWith(
(parent) =>
{
// Code here will execute after the parent task including its children have finished.
// You can safely update UI controls here.
}, TaskScheduler.FromCurrentSynchronizationContext);
}
else
{
MessageBox.Show("Please import data before attempting this procedure");
}
}
What I am doing here is creating a parent task that will itself spin up child tasks. Notice that I use TaskCreationOptions.AttachedToParent
to associate the child tasks with their parent. Then on the parent task I call ContinueWith
which gets executed after the parent and all of its children have completed. I use TaskScheduler.FromCurrentSynchronizationContext
to get the continuation to happen on the UI thread.
And here is an alternate solution using Parallel.ForEach
. Notice that it is a little bit cleaner solution.
private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e)
{
if (machineList.Count() != 0)
{
// Start the parent task.
var task = Task.Factory.StartNew(
() =>
{
Parallel.Foreach(machineList,
ws =>
{
fixClient(ws);
});
}, TaskCreationOptions.LongRunning);
// Define a continuation that happens after everything is done.
task.ContinueWith(
(parent) =>
{
// Code here will execute after the parent task has finished.
// You can safely update UI controls here.
}, TaskScheduler.FromCurrentSynchronizationContext);
}
else
{
MessageBox.Show("Please import data before attempting this procedure");
}
}
Upvotes: 2
Reputation: 21
Brian's solution is not complete and produces a syntax error. If not for the syntax error it would work and solve the initial poster's problem. I do not know how to fix the syntax error thus I'm posting this question for it to be resolved so that the initial question can be resolved. Please Do Not delete this message. it is pertinent to the initial question being answered.
@Brian Gideon: Your solution would be perfect with the exception of the following code:
// Define a continuation that happens after everything is done.
parent.ContinueWith(
() =>
{
// Code here will execute after the parent task has finished.
// You can safely update UI controls here.
}, TaskScheduler.FromCurrentSynchronizationContext);
The specific problem with this is in the () => portion. This is producing a syntax error which reads Delegate System.Action "System.Threading.Tasks.Task" does not take 0 arguments
I really wish this would work and I do not know the solution to this. I've tried to look up the error but I am not understanding what parameters its requiring. If anyone can answer this it would be extremely helpful. This is the only missing piece to this problem.
Upvotes: 2
Reputation: 41298
The way to do this would be to keep a reference to all the threads and then Join on them. This basically means that the current thread will block until the joined thread completes.
Change your loop to something like:
foreach (string ws in machineList)
{
var thread = new Thread(new ParameterizedThreadStart(fixClient), stack);
_machineThreads.Add(thread)
thread.Start();
}
(where _machineThreads is a list of System.Thread
)
You can then block until all are complete with something like:
private void WaitUntilAllThreadsComplete()
{
foreach (Thread machineThread in _machineThreads)
{
machineThread.Join();
}
}
HOWEVER - you almost certainly don't want to be doing this for the scenario you describe:
Upvotes: 12
Reputation: 109567
There is an alternative way of doing this that uses the CountdownEvent class.
The code that starts the threads must increment a counter, and pass a CountdownEvent object to each thread. Each thread will call CountdownEvent.Signal() when it is finished.
The following code illustrates this approach:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication6
{
class Program
{
static void Main(string[] args)
{
int numTasks = 20;
var rng = new Random();
using (var finishedSignal = new CountdownEvent(1))
{
for (int i = 0; i < numTasks; ++i)
{
finishedSignal.AddCount();
Task.Factory.StartNew(() => task(rng.Next(2000, 5000), finishedSignal));
}
// We started with a count of 1 to prevent a race condition.
// Now we must decrement that count away by calling .Signal().
finishedSignal.Signal();
Console.WriteLine("Waiting for all tasks to complete...");
finishedSignal.Wait();
}
Console.WriteLine("Finished waiting for all tasks to complete.");
}
static void task(int sleepTime, CountdownEvent finishedSignal)
{
Console.WriteLine("Task sleeping for " + sleepTime);
Thread.Sleep(sleepTime);
finishedSignal.Signal();
}
}
}
Upvotes: 2
Reputation: 24857
Never wait for thread completion, or anything else, in a GUI event handler. If you spawn many threads, (and yes, don't do that - see Rob's post), or submit many tasks to a threadpool, the last entitiy to complete execution should signal the GUI thread that the job is complete. Usually, this involves calling into some object that counts down the remaining tasks/threads and signals when the last one fires in. Look at System.Threading.CountdownEvent.
Upvotes: 2
Reputation: 7411
Another idea would be to use Parallel.ForEach in a seperate thread:
This is also safe if you have too many machines to fix
private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e)
{
if (machineList.Count() != 0)
{
AllFinished=False;
new Thread(new ThreadStart(fixAllClients).Start();
}
else
{
MessageBox.Show("Please import data before attempting this procedure");
}
}
private void fixAllClients(){
var options = new ParallelOptions{MaxDegreeOfParallelism=10};
Parallel.ForEach(machineList. options, fixClient);
AllFinished=True;
}
Upvotes: 2
Reputation: 236218
You could create WaitHandle
for each thread you are waiting for:
WaitHandle[] waitHandles = new WaitHandle[machineList.Count()];
Add ManualResetEvent
to list and pass it to thread:
for (int i = 0; i < machineList.Count(); i++)
{
waitHandles[i] = new ManualResetEvent(false);
object[] parameters = new object[] { machineList[i], waitHandles[i] };
new Thread(new ParameterizedThreadStart(fixClient), stack).Start(parameters);
}
// wait until each thread will set its manual reset event to signaled state
EventWaitHandle.WaitAll(waitHandles);
Inside you thread method:
public void fixClient(object state)
{
object[] parameters = (object[])state;
string ws = (string)parameters[0];
EventWaitHandle waitHandle = (EventWaitHandle)parameters[1];
// do your job
waitHandle.Set();
}
Main thread will continue execution when all threads will set their wait handles.
Upvotes: 1
Reputation: 903
you can use: IsAlive. But you have keep a reference like
Thread t = new Thread(new ThreadStart(...));
t.start();
if(t.IsAlive)
{
//do something
}
else
{
//do something else
}
Upvotes: 4