NET中如何等待线程完成?


178

我以前从未真正在C#中使用过线程,在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();
}

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


4
如果您“ ...以前从未使用过线程...”,则可能需要考虑一些更简单的替代方法,例如* Async()模式。
2009年

4
如果您只是在等待线程1完成,为什么不同步调用该方法呢?
Svish 2010年

13
当您以线性方式进行处理时,使用线程有什么意义?
约翰2010年

1
@John,对我来说完全有意义,它有很多用途,可以在用户工作时分离出一个可以工作的后台线程。另外,您的问题与上一个问题不同吗?
user34660

Rotem的答案,使用backgroundworker易于使用,非常简单。
卡米尔

Answers:


264

我可以看到5个可用选项:

1. Thread.Join

与米奇的答案一样。但这会阻塞您的UI线程,但是您会为您内置一个超时。


2.使用 WaitHandle

ManualResetEventWaitHandle克里斯蒂娜建议的。

需要注意的一件事是,如果您要等待多个线程,WaitHandle.WaitAll()则默认情况下将不起作用,因为它需要一个MTA线程。您可以通过用Main()方法标记您的方法来解决此问题MTAThread-但这会阻塞您的消息泵,因此根据我的阅读,不建议这样做。


3.触发事件

请参阅乔恩·斯基特(Jon Skeet)的有关事件和多线程的页面,事件可能在if和之间取消订阅EventName(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();
        }
    }
}

如果您确实使用_count方法,则可能会(为了安全起见)使用以下方法增加它

Interlocked.Increment(ref _count)

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


5.异步执行

这个问题的答案非常清楚地说明了使用此方法的选择。


代表/事件在错误的线程上

事件/委托的处理方式将意味着您的事件处理程序方法位于thread1 / thread2 而非主UI线程上,因此您需要在HandleThreadDone方法的顶部重新切换:

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

61

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

在您启动它之后,但这并不能完成太多的事情,因为与运行在主线程上的结果基本相同!

如果您想了解.NET方面的知识,我强烈建议您阅读Joe Albahari的C#免费电子书。


4
即使Join从字面上看是问询者所要求的,这通常是非常糟糕的。调用Join将挂断从中进行此操作的线程。如果这恰好是主GUI线程,则为BAD!作为用户,我积极讨厌似乎可以这种方式运行的应用程序。所以,请所有其他答案这个问题,并stackoverflow.com/questions/1221374/...
peSHIr

1
我同意一般来说Join()是不好的。我的回答也许不够明显。
米奇·

2
伙计们,一种尺寸并不适合所有人。在某些情况下,确实需要确定该线程已完成其工作:考虑该线程正在处理即将更改的数据。在这种情况下,通知IMO完全合理地通知线程正常取消并等待其完成(尤其是在非常快速地处理了一个步骤时)。我宁愿说,Join是邪恶的(用C ++ FAQ术语来说),即。除非确实需要,否则不得使用它。
惊吓2012年

5
我想明确指出,Join是一种工具,尽管事实上它经常被滥用,但它还是有用的。在某些情况下,它可以正常工作而不会产生任何不必要的副作用(例如,将主GUI线程停滞一段明显的时间)。
惊吓2012年

2
加入是工具吗?我想您会发现这是一种方法。
米奇·麦特

32

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

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是.NET框架必须提供的各种WaitHandle之一。与简单但非常常见的工具(例如lock()/ Monitor,Thread.Join等)相比,它们可以提供更丰富的线程同步功能。它们还可以用于同步两个以上的线程,从而允许诸如“主”线程之类的复杂场景协调多个“子”线程,依赖于彼此要同步的多个阶段的多个并发进程等。


29

如果从.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
    }
}

来自:https : //stackoverflow.com/a/4190969/1676736


8
您没有在回答中提及任何有关线程的内容。问题是关于线程,而不是任务。两者不一样。
Suamere

7
我提出了与原始海报相似的问题(和知识水平),这个答案对我来说非常有价值-任务更适合我在做的事情,如果我找不到这个答案,我会写我自己的自己可怕的线程池。
克里斯·雷

1
@ChrisRae,所以这应该是原始问题的评论,而不是像这样的答案。
Jaime Hablutzel,

关于这个具体答案,我认为在这里可能更有意义。
克里斯·雷

1
正如@Suamere所说,此答案与OP问题完全无关。
Glenn Slayden


4

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


0

试试这个:

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

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

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

0

张贴文章以帮助其他人,花费了很多时间来寻找像我想出的解决方案。所以我采取了一些不同的方法。上面有一个计数器选项,我只是应用了一点不同。当线程启动和停止时,我正在拆分多个线程,并增加了一个计数器,并减少了一个计数器。然后在主要方法中,我想暂停并等待线程完成,所以我做了。

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/


4
这称为忙等待。是的,它可以工作,有时是最好的解决方案,但是如果可能的话,您要避免使用它,因为这会浪费CPU时间
MobileMon 2012年

@MobileMon不仅仅是一条准则。在这种情况下,由于该循环可能会浪费0.00000001%的CPU,并且OP用C#进行编码,因此用“更有效的”替换该代码将完全浪费时间。优化的第一条规则是-不。首先测量。
Spike0xff '16

0

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

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

-1

这是一个简单的示例,在同一类中等待踩完。它还会调用同一命名空间中的另一个类。我包括了“ using”语句,因此只要您创建button1,它就可以作为Winform执行。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace ClassCrossCall
{

 public partial class Form1 : Form
 {
  int number = 0; // this is an intentional problem, included for demonstration purposes
  public Form1()
  {
   InitializeComponent();
  }
  private void Form1_Load(object sender, EventArgs e)
  {
   button1.Text="Initialized";
  }
  private void button1_Click(object sender, EventArgs e)
  {
   button1.Text="Clicked";
   button1.Refresh();
   Thread.Sleep(400);
   List<Task> taskList = new List<Task>();
   taskList.Add(Task.Factory.StartNew(() => update_thread(2000)));
   taskList.Add(Task.Factory.StartNew(() => update_thread(4000)));
   Task.WaitAll(taskList.ToArray());
   worker.update_button(this,number);
  }
  public void update_thread(int ms)
  {
   // It's important to check the scope of all variables
   number=ms; // this could be either 2000 or 4000.  Race condition.
   Thread.Sleep(ms);
  }
 }

 class worker
 {
  public static void update_button(Form1 form, int number)
  {
   form.button1.Text=$"{number}";
  }
 }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.