【问题标题】:Multithreading: When would I use a Join?多线程:我什么时候使用 Join?
【发布时间】:2010-12-19 23:35:22
【问题描述】:

我在网上看到它说我使用myThread.Join(); 当我想阻塞我的线程直到另一个线程完成时。 (我不明白的一件事是如果我有多个线程会怎样)。

但一般来说,我只是不明白何时使用.Join() 或它有用的条件。谁能像我四年级一样向我解释一下?非常简单易懂的解释会得到我的回答投票。

【问题讨论】:

    标签: c# multithreading


    【解决方案1】:

    假设您想启动一些工作线程来执行某种计算,然后对所有结果执行一些操作。

    List<Thread> workerThreads = new List<Thread>();
    List<int> results = new List<int>();
    
    for (int i = 0; i < 5; i++) {
        Thread thread = new Thread(() => {
            Thread.Sleep(new Random().Next(1000, 5000));
            lock (results) {
                results.Add(new Random().Next(1, 10));
            }
        });
        workerThreads.Add(thread);
        thread.Start();
    }
    
    // Wait for all the threads to finish so that the results list is populated.
    // If a thread is already finished when Join is called, Join will return immediately.
    foreach (Thread thread in workerThreads) {
        thread.Join();
    }
    
    Debug.WriteLine("Sum of results: " + results.Sum());
    

    哦,是的,不要像那样使用 Random,我只是想写一个简单易懂的例子。如果您在时间上太接近创建新的 Random 实例,它最终并不是真正随机的,因为种子是基于时钟的。

    【讨论】:

    • 你需要添加类似'int value = i;'在你初始化一个新线程之前的循环中。因为 'i' 可以在线程开始之前增加,并且总和将是不确定的。
    【解决方案2】:

    在下面的代码 sn-p 中,主线程调用 Join() 导致它等待所有派生线程完成:

    static void Main()
    {
        Thread regularThread = new Thread(ThreadMethod);
        regularThread.Start();
    
        Thread regularThread2 = new Thread(ThreadMethod2);
        regularThread2.Start();
    
        // Wait for spawned threads to end.
        regularThread.Join();
        Console.WriteLine("regularThread returned.");
    
        regularThread2.Join();
        Console.WriteLine("regularThread2 returned.");
    }
    

    请注意,如果您还从线程池中启动了一个线程(例如使用 QueueUserWorkItem),Join 将不会等待该后台线程。您需要实现一些其他机制,例如使用 AutoResetEvent。

    有关线程的精彩介绍,我建议阅读 Joe Albahari 的免费 Threading in C#

    【讨论】:

      【解决方案3】:

      这是一个非常简单的程序,用于演示线程Join的用法。请关注我的cmets以便更好地理解。按原样编写此程序。

          using System;
          using System.Threading;
      
      
          namespace ThreadSample
          {
              class Program
              {
                  static Thread thread1, thread2;
                  static int sum=0;
                  static void Main(string[] args)
                  {
                      start();
                      Console.ReadKey();
                  }
                  private static void Sample() { sum = sum + 1; }
                  private static void Sample2() { sum = sum + 10; }
      
                  private static void start() 
                  {    
                      thread1 = new Thread(new ThreadStart(Sample));
                      thread2 = new Thread(new ThreadStart(Sample2));
                      thread1.Start();
                      thread2.Start();
                   // thread1.Join(); 
                   // thread2.Join();
                      Console.WriteLine(sum);
                      Console.WriteLine();
                  }
             }
      }
      

      1.第一次运行原样(使用 cmets)然后结果将是 0(初始值)或 1(当线程 1 完成时)或 10(或线程完成)

      2.Run with removing comment thread1.Join() : 结果应该总是大于 1。因为thread1.Join() 被触发并且线程 1 应该在得到总和之前完成.

      3.删除所有评论运行:结果应始终为 11

      【讨论】:

      • 第 2 点可能有一些措辞错误。您的线程 1 和线程 2 只会运行 1 次,因为没有看到循环(for、while)。因此总和值将是 0、1、10 或 11。第 2 点中的“超过 1”可以更好地细化以更有意义。
      • 当执行 3 时,结果并不总是 11。 thread1thread2 仍然可以读取 的值sum 为 0 并在执行分配回 sum 之前执行加法。至少,您必须在计算周围锁定一个对象以确保线程安全。 private static object locker = new object(); 然后lock (locker) { sum = sum + 1; }
      【解决方案4】:

      Join 主要用于当您需要等待一个线程(或一组线程)终止后再继续执行您的代码时。

      因此,当您需要从线程执行中收集结果时也特别有用。

      根据下面的 Arafangion 评论,如果您在创建线程后需要执行一些清理/内务管理代码,那么加入线程也很重要。

      【讨论】:

      • 应该提到这在清理准备退出的进程时可能很重要。
      【解决方案5】:

      Join 将确保在执行下面的行之前执行上面的代码。

      【讨论】:

        【解决方案6】:

        另一个例子,假设你的工作线程从输入流中读取,而 read 方法可以永远运行,而你想以某种方式避免这种情况 - 通过使用另一个看门狗线程应用超时:

        // worker thread
        var worker = new Thread(() => {
            Trace.WriteLine("Reading from stream");
        
            // here is the critical area of thread, where the real stuff happens
            // Sleep is just an example, simulating any real operation
            Thread.Sleep(10000);
        
            Trace.WriteLine("Reading finished");
        }) { Name = "Worker" };
        Trace.WriteLine("Starting worker thread...");
        worker.Start();
        
        // watchdog thread
        ThreadPool.QueueUserWorkItem((o) => {
            var timeOut = 5000;
            if (!worker.Join(timeOut))
            {
                Trace.WriteLine("Killing worker thread after " + timeOut + " milliseconds!");
                worker.Abort();
            }
        });
        

        【讨论】:

          【解决方案7】:

          在 devopsEMK 的帖子中在方法“Sample”中添加 300 毫秒的延迟和在“Sample2”中添加 400 毫秒的延迟会更容易理解。

          通过这样做,您可以通过从“thread1.Join();”中删除注释来观察到这一点行,主线程等待“thread1”完成,并且只有在继续之后。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-05-03
            • 1970-01-01
            • 2011-08-08
            • 1970-01-01
            • 1970-01-01
            • 2021-11-06
            • 2011-03-22
            相关资源
            最近更新 更多