【问题标题】:How can I wait for a thread to finish with .NET?如何等待线程完成 .NET?
【发布时间】:2010-12-07 17:32:04
【问题描述】:

我以前从未在 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();
}

那么,本质上,我怎样才能让一个线程等待另一个线程完成?最好的方法是什么?

【问题讨论】:

  • 如果您只是在等待线程 1 完成,为什么不同步调用该方法?
  • 以线性方式处理时使用线程有什么意义?
  • @John,对我来说完全有意义的是,在用户工作时分离一个工作的后台线程有很多用途。还有,你的问题是不是和上一个一样?
  • Rotem's answer,使用backgroundworker方便使用,很简单。

标签: c# multithreading


【解决方案1】:

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

1。线程.Join

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


2。使用WaitHandle

ManualResetEvent 是 jrista 建议的 WaitHandle

需要注意的一点是,如果你想等待多个线程:WaitHandle.WaitAll() 默认不会工作,因为它需要一个MTA 线程。您可以通过使用 MTAThread 标记您的 Main() 方法来解决此问题 - 但是这会阻止您的消息泵,并且不推荐从我所阅读的内容。


3。触发事件

请参阅this page by Jon Skeet,了解事件和多线程。 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();
        }
    }
}

如果您确实使用了 _count 方法,那么(为了安全起见)增加它可能是一个想法

Interlocked.Increment(ref _count)

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


5。改为异步执行

this question 的答案非常清楚地描述了您使用此方法的选项。


错误线程上的委托/事件

事件/委托的处理方式意味着您的事件处理程序方法在线程 1/线程2 不是主 UI 线程,因此您需要向右切换在 HandleThreadDone 方法的顶部:

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

【讨论】:

    【解决方案2】:

    添加

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

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

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

    【讨论】:

    • 尽管Join 确实是提问者所要求的,但总的来说这可能非常糟糕。对Join 的调用将挂起执行此操作的线程。如果这恰好是主 GUI 线程,那就是 BAD!作为用户,我非常讨厌似乎以这种方式工作的应用程序。所以请查看这个问题的所有其他答案和stackoverflow.com/questions/1221374/…
    • 我同意总的来说 Join() 是不好的。我的回答可能不够明显。
    • 伙计们,一种尺寸并不适合所有人。在某些情况下,确实需要确定该线程已完成其工作:考虑一下,该线程正在处理即将更改的数据。在这种情况下,通知线程优雅地取消并等待它完成(特别是当一个步骤处理得非常快时)是 IMO 完全合理的。我宁愿说,Join 是 evil (用 C++ FAQ 术语),即。除非真的需要,否则不得使用。
    • 我想明确说明,Join 是一种工具,它很有用,尽管它经常被滥用。在某些情况下,它会在没有任何不必要的副作用的情况下正常工作(例如将主 GUI 线程暂停一段时间)。
    • 加入是工具?我想你会发现这是一种方法。
    【解决方案3】:

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

    发件人:Create multiple threads and wait all of them to complete

    【讨论】:

    • 您在回答中没有提到任何有关线程的内容。问题是关于线程,而不是任务。两者不一样。
    • 我提出了与原始海报类似的问题(和知识水平),这个答案对我来说非常有价值 - 任务更适合我正在做的事情,如果我没有找到这个答案我会写我自己可怕的线程池。
    • @ChrisRae,所以这应该是原始问题中的评论,而不是像这样的答案。
    • 正如@Suamere 所说,这个答案与OP 问题完全无关。
    • 它可能与原始问题无关,因为它使用任务,但它完成完全相同的事情。 OP 表示他们从未在 C# 中完成线程等操作,并且可能只是在询问线程,因为他不知道任务的存在。这很简单,优雅,就像一个魅力。附带说明一下,如果您要启动 x 数量的任务,您可以 WaitAll 处理一系列任务。
    【解决方案4】:

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

    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 等简单但非常常见的工具更丰富的线程同步功能。

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

    【讨论】:

      【解决方案5】:

      您想要Thread.Join() 方法,或其overloads 之一。

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

          试试这个:

          List<Thread> myThreads = new List<Thread>();
          
          foreach (Thread curThread in myThreads)
          {
              curThread.Start();
          }
          
          foreach (Thread curThread in myThreads)
          {
              curThread.Join();
          }
          

          【讨论】:

            【解决方案8】:

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

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

            【讨论】:

              【解决方案9】:

              我采取了一些不同的方法。以前的答案中有一个计数器选项,我只是应用它有点不同。当线程启动和停止时,我正在关闭许多线程并增加一个计数器并减少一个计数器。然后在 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/

              【讨论】:

              • 这称为忙等待。是的,它可以工作,有时是最好的解决方案,但如果可能的话,你想避免它,因为它会浪费 CPU 时间
              • @MobileMon 这更像是一个指导方针而不是规则。在这种情况下,由于该循环可能会浪费 0.00000001% 的 CPU 并且 OP 使用 C# 进行编码,因此将其替换为“更高效”的东西将完全浪费时间。优化的第一条规则是 - 不要。先测量。
              • 链接已损坏(“Yaiks!找不到您要查找的内容。”) - 但域没有,请随时修复它(任何人 - OP 已经离开大楼)。在您访问时穿上裸链接(使用标题而不是显示 URL)。
              【解决方案10】:

              此实现与基于ManualResetEvent 的@jrista 示例略有不同,因为它显示了各种选项就像红绿灯一样。

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

              【讨论】:

              • 这对我有用
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-11-18
              • 2015-08-29
              • 2011-06-09
              • 2010-10-16
              相关资源
              最近更新 更多