如何使用.NET等待线程完成?

我以前从来没有在C#中真正使用过线程,我需要有两个线程,以及主UI线程。基本上,我有以下几点。

public void StartTheActions()
{
// Starting thread 1....
Thread t1 = 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();
}

因此,从本质上讲,我如何让一个线程等待另一个线程完成?做这件事的最好方法是什么?

401087 次浏览

t1.Join();    // Wait until thread t1 finishes

在你启动它之后,但这不会完成太多,因为它本质上与在主线程上运行的结果相同!

如果您想了解.NET中的线程,我强烈推荐您阅读Joe Albahari的C#中的线程免费电子书。

您需要Thread.Join()方法或其超载之一。

前面的两个答案很好,适用于简单的场景。但是,还有其他同步线程的方法。以下内容也将起作用:

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();
}

手动复位事件是.NET框架必须提供的各种韦特汉德尔中的一种。与lock()/monitor、Thread.join等简单但非常常见的工具相比,它们可以提供更丰富的线程同步功能。

它们还可用于同步两个以上的线程,从而允许诸如协调多个“子”线程的“主”线程、要同步的依赖于彼此的多个阶段的多个并发进程等复杂场景。

我会让你的主线程将一个回调方法传递给你的第一个线程,当它完成时,它将调用主线程上的回调方法,这可以启动第二个线程。这可以防止主线程在等待join或waitHandle时挂起。无论如何,将方法作为委托传递是学习C#的一件有用的事情。

我可以看到五个可用的选项:

1.线程.加入

就像米奇的回答一样。但是这会阻塞你的UI线程,但是你会得到一个内置的超时。


使用WaitHandle

ManualResetEvent是如Jrista所建议的WaitHandle

需要注意的一点是,如果您希望等待多个线程:默认情况下,WaitHandle.WaitAll()将无法工作,因为它需要一个MTA线程。你可以通过用MTAThread标记你的Main()方法来解决这个问题——但是这会阻塞你的消息泵,从我读到的内容来看,这是不推荐的。


激发事件

有关事件和多线程处理,请参阅本页作者:乔恩·斯基特ifEventName(this,EventArgs.Empty)之间的事件可能会变得无法接收-我以前就遇到过这种情况。

(希望这些编译,我还没有尝试)

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.使用委托

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();
}
}
}

如果您确实使用_计数方法,那么(为了安全起见)使用以下方式增加它可能是个好主意

Interlocked.Increment(ref _count)

我很想知道使用委托和事件进行线程通知之间的区别,我知道的唯一区别是事件是同步调用的。


5.改为异步执行

这个问题的答案非常清楚地描述了您使用此方法的选项。


在错误的线程上委托/事件

事件/委托的执行方式意味着您的事件处理程序方法位于Thread1/Thread2而不是主UI线程上,因此您需要在HandleThreadDone方法的顶部切换回来:

// Delegate example
if (InvokeRequired)
{
Invoke(new Action(HandleThreadDone));
return;
}

我采取了一点不同的方法。在前面的答案中有一个相反的选项,我只是稍微不同地应用了它。当一个线程开始和停止时,我正在旋转多个线程,并增加一个计数器和减少一个计数器。然后在main方法中,我想暂停并等待线程完成,我做到了。

while (threadCounter > 0)
{
Thread.Sleep(500); // Make it pause for half second so that we don’t spin the CPU out of control.
}

这记录在我的博客文章中:http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/.

试试这个:

List<Thread> myThreads = new List<Thread>();


foreach (Thread curThread in myThreads)
{
curThread.Start();
}


foreach (Thread curThread in myThreads)
{
curThread.Join();
}

如果使用.NET 4,此示例可以帮助您:

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
}
}

来自:创建多个线程并等待所有线程完成

当我希望UI能够在等待任务完成时更新其显示时,我使用了一个while循环来测试线程上的IsAlive:

    Thread t = new Thread(() => someMethod(parameters));
t.Start();
while (t.IsAlive)
{
Thread.Sleep(500);
Application.DoEvents();
}


这个实现与@Jrista的基于ManualResetEvent的示例稍有不同,因为它显示了各种选项如何类似于红色或绿色交通灯。

    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");
}

另一种方法是在一个线程中使用lock(someobject)和monitor.wait(someobject[,timeout]),在另一个线程中使用lock(someobject)和monitor.pulse(someobjects)。在所有4次调用中,SomeObject必须是类的同一实例。SomeObject不能是结构。

第一个线程锁定SomeObject,然后调用Monitor.wait()释放锁定,因此第二个线程可以锁定SomeObject.当第二个线程完成时,它调用monitor.pulse(),然后第一个线程的monitor.wait()结束。

示例:SomeObject是一个队列,第一个线程等待第二个线程将对象放入队列中,然后将该对象出队。