How can I wait for a thread to finish with .NET?
I've never really used threading before in C# where I need to have two threads, as well as the main UI thread. Basically, I have the following.
public void StartTheActions()
{
// Starting thread 1....
Thread t开发者_运维问答1 = new Thread(new ThreadStart(action1));
t1.Start();
// Now, I want for the main thread (which is calling `StartTheActions` method)
// to wait for `t1` to finish. I've created an event in `action1` for this.
// The I wish `t2` to start...
Thread t2 = new Thread(new ThreadStart(action2));
t2.Start();
}
So, essentially, how can I have a thread wait for another one to finish? What is the best way to do this?
I can see five options available:
1. Thread.Join
As with Mitch's answer. But this will block your UI thread, however you get a Timeout built in for you.
2. Use a WaitHandle
ManualResetEvent
is a WaitHandle
as jrista suggested.
One thing to note is if you want to wait for multiple threads: WaitHandle.WaitAll()
won't work by default, as it needs an MTA thread. You can get around this by marking your Main()
method with MTAThread
- however this blocks your message pump and isn't recommended from what I've read.
3. Fire an event
See this page by Jon Skeet about events and multi-threading. It's possible that an event can become unsubcribed between the if
and the EventName(this,EventArgs.Empty)
- it's happened to me before.
(Hopefully these compile, I haven't tried)
public class Form1 : Form
{
int _count;
void ButtonClick(object sender, EventArgs e)
{
ThreadWorker worker = new ThreadWorker();
worker.ThreadDone += HandleThreadDone;
Thread thread1 = new Thread(worker.Run);
thread1.Start();
_count = 1;
}
void HandleThreadDone(object sender, EventArgs e)
{
// You should get the idea this is just an example
if (_count == 1)
{
ThreadWorker worker = new ThreadWorker();
worker.ThreadDone += HandleThreadDone;
Thread thread2 = new Thread(worker.Run);
thread2.Start();
_count++;
}
}
class ThreadWorker
{
public event EventHandler ThreadDone;
public void Run()
{
// Do a task
if (ThreadDone != null)
ThreadDone(this, EventArgs.Empty);
}
}
}
4. Use a delegate
public class Form1 : Form
{
int _count;
void ButtonClick(object sender, EventArgs e)
{
ThreadWorker worker = new ThreadWorker();
Thread thread1 = new Thread(worker.Run);
thread1.Start(HandleThreadDone);
_count = 1;
}
void HandleThreadDone()
{
// As before - just a simple example
if (_count == 1)
{
ThreadWorker worker = new ThreadWorker();
Thread thread2 = new Thread(worker.Run);
thread2.Start(HandleThreadDone);
_count++;
}
}
class ThreadWorker
{
// Switch to your favourite Action<T> or Func<T>
public void Run(object state)
{
// Do a task
Action completeAction = (Action)state;
completeAction.Invoke();
}
}
}
If you do use the _count method, it might be an idea (to be safe) to increment it using
Interlocked.Increment(ref _count)
I'd be interested to know the difference between using delegates and events for thread notification, the only difference I know are events are called synchronously.
5. Do it asynchronously instead
The answer to this question has a very clear description of your options with this method.
Delegate/Events on the wrong thread
The event/delegate way of doing things will mean your event handler method is on thread1/thread2 not the main UI thread, so you will need to switch back right at the top of the HandleThreadDone methods:
// Delegate example
if (InvokeRequired)
{
Invoke(new Action(HandleThreadDone));
return;
}
Add
t1.Join(); // Wait until thread t1 finishes
after you start it, but that won't accomplish much as it's essentialy the same result as running on the main thread!
I can highly recommended reading Joe Albahari's Threading in C# free e-book, if you want to gain an understanding of threading in .NET.
If using from .NET 4 this sample can help you:
class Program
{
static void Main(string[] args)
{
Task task1 = Task.Factory.StartNew(() => doStuff());
Task task2 = Task.Factory.StartNew(() => doStuff());
Task task3 = Task.Factory.StartNew(() => doStuff());
Task.WaitAll(task1, task2, task3);
Console.WriteLine("All threads complete");
}
static void doStuff()
{
// Do stuff here
}
}
From: Create multiple threads and wait all of them to complete
The previous two answers are great and will work for simple scenarios. There are other ways to synchronize threads, however. The following will also work:
public void StartTheActions()
{
ManualResetEvent syncEvent = new ManualResetEvent(false);
Thread t1 = new Thread(
() =>
{
// Do some work...
syncEvent.Set();
}
);
t1.Start();
Thread t2 = new Thread(
() =>
{
syncEvent.WaitOne();
// Do some work...
}
);
t2.Start();
}
ManualResetEvent is one of the various WaitHandle's that the .NET framework has to offer. They can provide much richer thread synchronization capabilities than the simple, but very common tools like lock()/Monitor, Thread.Join, etc.
They can also be used to synchronize more than two threads, allowing complex scenarios such as a 'master' thread that coordinates multiple 'child' threads, multiple concurrent processes that are dependent upon several stages of each other to be synchronized, etc.
You want the Thread.Join()
method, or one of its overloads.
I would have your main thread pass a callback method to your first thread, and when it's done, it will invoke the callback method on the mainthread, which can launch the second thread. This keeps your main thread from hanging while its waiting for a Join or Waithandle. Passing methods as delegates is a useful thing to learn with C# anyway.
This implementation is a little different from @jrista's example based on ManualResetEvent
as it shows how the various options are like a red or green traffic light.
public System.Threading.AutoResetEvent thread1done = new System.Threading.AutoResetEvent(false);
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
{
thread1done.Set(); //set traffic light to green before threading
StartTheActions();
}
public void StartTheActions()
{
Thread t1 = new Thread(action1);
t1.Start();
thread1done.WaitOne(); //traffic light is red, until thread1done.Set inside action1()
Thread t2 = new Thread(action2);
t2.Start();
}
public void action1()
{
Thread.Sleep(5000);
//.... do some work
thread1done.Set(); //work is done, set traffic light to green at thread1done.WaitOne()
}
public void action2()
{
MessageBox.Show("Now processing action2");
}
Try this:
List<Thread> myThreads = new List<Thread>();
foreach (Thread curThread in myThreads)
{
curThread.Start();
}
foreach (Thread curThread in myThreads)
{
curThread.Join();
}
When I want the UI to be able to update its display while waiting for a task to complete, I use a while-loop that tests IsAlive on the thread:
Thread t = new Thread(() => someMethod(parameters));
t.Start();
while (t.IsAlive)
{
Thread.Sleep(500);
Application.DoEvents();
}
I took a little different approach. There is a counter option in previous answers, and I just applied it a bit differently. I was spinning off numerous threads and incremented a counter and decremented a counter as a thread started and stopped. Then in the main method I wanted to pause and wait for threads to complete I did.
while (threadCounter > 0)
{
Thread.Sleep(500); // Make it pause for half second so that we don’t spin the CPU out of control.
}
This is documented in my blog post: http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/
Another method is using lock(someObject) and Monitor.Wait(someObject[,timeout]) in one thread and lock(someObject) and Monitor.Pulse(someObject) in another thread. SomeObject has to be the same instance of a class in all 4 calls. SomeObject can't be a struct.
The first thread locks someObject and then calls Monitor.Wait() which releases the lock, so the second thread can lock someObject. When the second thread is finished it calls Monitor.Pulse(), and then the first thread's Monitor.Wait() ends.
Example: someObject is a queue, the first threads waits for the second to put an object in the queue and then dequeues that object.
精彩评论