如何正确检测另一个线程中的多个线程何时完全完成?

How to properly detect when multiple threads within another thread are fully complete?

我是 运行 几个 BackgroundWorker 线程,它们被用来执行查询以检索数据集,所有线程都在另一个 BackgroundWorker 线程中。我们将 运行 这些多个线程称为 'Host Thread' 和其他 'Query Thread'。我想要做的是通过利用主机线程的 RunWorkerCompleted 事件来告知所有查询线程何时完成填充它们的数据集。此事件处理程序中的第一行是

while (dataSets.Count < count) { Thread.Sleep(100); } //dataSets is a Dictionary<string, DataSet>

其中 count 是预期返回的数据集的总数。我的问题似乎是 dataSets.Count 在填充所有数据集之前似乎变成了 count

这是我的完整代码(Unhelpful/Sensitive 信息已删除)

        var hostThread = new BackgroundWorker();
        hostThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(queryWorker_RunWorkerCompleted);
        hostThread.DoWork += (send, even) =>
            {
                foreach (var cs in _connectionStrings)
                {
                    var queryThread = new BackgroundWorker();
                    queryThread.DoWork += (se, eve) =>
                    {
                        var set = DataHandlers.TryGetDataSet(_query, cs, domain, username, pass);
                        dataSets.Add(((DataRow)set.Tables[0].Rows[0]).ItemArray[0].ToString(), set);
                    };
                    queryThread.RunWorkerAsync();
                }
            };
        hostThread.RunWorkerAsync();

RunWorker 已完成:

 var bw = new BackgroundWorker();
        bw.DoWork += (s, ev) =>
            {
                //Waiting for all DataSets to get populated
                while (dataSets.Count < count) { Thread.Sleep(100); }
                //Thread.Sleep(5000); If I add this, everything works fine, but when I start running more queries in each query thread this needs to be increased.
                this.Invoke((MethodInvoker)delegate()
                {
                    this.Cursor = Cursors.Default;
                    this.Hide();
                    foreach (var set in dataSets)
                    {
                            if (set == null)
                                break;
                            //THIS BLOCK IS NEVER HIT IF I LEAVE OUT THE FIVE SECOND SLEEP
                            var workflowList = new List<string>();
                            foreach (var row in set.Value.Tables[0].Rows)
                            {
                                workflowList.Add(((DataRow)row).ItemArray[_licensed ? 1 : 0].ToString());
                            }
                            ((MainForm)this.OwnedForms[0]).ClientWorkflows = new KeyValuePair<string, List<string>>(set.Key, workflowList);
                    }
                    //This gets hit before setting properties on a child form because it still thinks there are no DataSets in the dataSets dictionary
                    ((MainForm)this.OwnedForms[0]).ShowDialog();
                    this.Close();
                });

            };
        bw.RunWorkerAsync();

因此,正如我在代码的注释中所述 - 我知道在某些时候,只要我在 while 循环之后添加足够长的睡眠时间,数据集就会有效。那么,判断所有查询线程何时 实际上 在主机线程完成事件处理程序中完成的最佳方法是什么?

编辑:Per @ndd 这就是我最终使用的。

  var queryTasks = new List<Task>();

        var parentTask = Task.Factory.StartNew(() =>
            {
                foreach (var cs in appConfigStrings)
                {
                    queryTasks.Add(Task.Factory.StartNew(() => GetDataSets(mainForm, cs.Key, cs.Value)));
                }
                var array = queryTasks.ToArray();
                Task.WaitAll(array);
            });

        parentTask.ContinueWith((t) =>
            {
                this.Invoke((MethodInvoker)delegate()
               {
                   this.Cursor = Cursors.Default;
                   this.Hide();
                   foreach (var set in dataSets)
                   {
                       var workflowList = new List<string>();
                       foreach (var row in set.Value.Tables[0].Rows)
                       {
                           workflowList.Add(((DataRow)row).ItemArray[_licensed ? 1 : 0].ToString());
                       }
                       ((MainForm)this.OwnedForms[0]).ClientWorkflows = new KeyValuePair<string, List<string>>(set.Key, workflowList);
                   }
                   ((MainForm)this.OwnedForms[0]).ShowDialog();
                   this.Close();
               });
            });

就我个人而言,我从不赞成睡眠,因为它不可预测。如果我必须使用 BackgroundWorker,那么我可能会使用 IsBusy 属性 来确定 BackgroundThread 是否完成。

带有 TPL 的示例代码,请注意这只是一个示例,在现实世界中您可能需要处理异常、传递取消令牌和其他东西:)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BGToTPL
{
    class Program
    {
        static void Main(string[] args)
        {
            Task[] tasks = new Task[20];
            //Parent task is starting 20 child tasks
            var parentTask = Task.Run(() =>
            {
                Console.WriteLine("Parent threadid: " + System.Threading.Thread.CurrentThread.ManagedThreadId);
                for (int i = 0; i < 20; i++)
                {
                    tasks[i] = Task.Factory.StartNew(() =>
                    {
                        Console.WriteLine("Child threadid: " + System.Threading.Thread.CurrentThread.ManagedThreadId);                      
                        Task.Delay(15000);
                    });
                }
            });

            parentTask.Wait();

            Console.WriteLine("Parent task has started creating and running all the child tasks, now waiting for child tasks to be over.");

            //Now wait for all the tasks to be done
            Task.WaitAll(tasks);

            Console.WriteLine("All the tasks are done");
            Console.ReadKey();
        }
    }
}

和输出

这样的事情怎么样。 — 当然,如果没有 TPL 选项:

  private readonly IList<BackgroundWorker> workers = new List<BackgroundWorker>();

  private void Run()
  {
     var worker1 = new BackgroundWorker();
     worker1.DoWork += (sender, args) => Thread.Sleep(1000);
     worker1.RunWorkerCompleted += (sender, args) => this.CheckThreads();

     var worker2 = new BackgroundWorker();
     worker2.DoWork += (sender, args) => Thread.Sleep(1000);
     worker2.RunWorkerCompleted += (sender, args) => this.CheckThreads();

     lock (this.workers)
     {
        this.workers.Add(worker1);
        this.workers.Add(worker2);
     }

     worker1.RunWorkerAsync();
     worker2.RunWorkerAsync();
  }

  private void CheckThreads()
  {
     lock (this.workers)
     {
        if (this.workers.All(w => !w.IsBusy))
        {
           Console.WriteLine("All workers completed");
        }
     }
  }