【问题标题】:How to properly detect when multiple threads within another thread are fully complete?如何正确检测另一个线程中的多个线程何时完全完成?
【发布时间】:2015-10-30 16:57:59
【问题描述】:

我正在运行几个BackgroundWorkerthreads,它们用于执行查询以在另一个BackgroundWorker 线程中检索所有数据集。让我们将运行这些多个线程的线程称为“主机线程”,将其他线程称为“查询线程”。我要做的是通过利用主机线程的 RunWorkerCompleted 事件来判断所有查询线程何时完成填充其数据集。此事件处理程序的第一行是

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

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

这是我的完整代码(无用/敏感信息已删除)

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

RunWorkerCompleted:

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

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

编辑:根据@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();
               });
            });

【问题讨论】:

  • 是否使用TPL 选项?
  • 哇,我还没有被介绍到任务并行性。看了一会儿,我就知道这绝对是我最好的选择。我仍然对为什么我的解决方案无法工作感到困惑。编辑:实际上我仅限于.net fw 4.0。该死! @ndd
  • 您不能只等待一些时间,这可能会花费太长时间,并且如果其中一个 BackgroundWorkers 遇到异常,它将永远不会返回其数据集。您需要为每个查询线程提供一种方法来告诉主机线程它们已完成。一种方法是对它在完成时设置的每个查询线程使用AutoResetEvent,并让主机线程等待所有这些。我也不知道 TPL,所以这对我来说是显而易见的方法。

标签: c# .net multithreading winforms backgroundworker


【解决方案1】:

就我个人而言,我从不赞成睡眠,因为它是不可预测的。如果我必须使用 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();
        }
    }
}

还有输出

【讨论】:

  • 这是完美的。奇迹般有效!!我必须更改一些东西才能使用 .NET FW 4,但总体概念正是我想要的。我用解决方案编辑了我的问题。
  • 你能详细说明为什么睡眠是不可预测的吗?是一般精度问题还是只是 OP 的使用上下文?
  • @StingyJack 我的意思是你永远不知道你的线程需要多长时间才能完成,而睡眠可能只会进一步减慢进程,我会尝试用一个例子来解释一个线程需要 51 秒完成并假设内部循环睡眠存在 5 秒(每次检查状态后),因此在这种情况下,代码可能最终会比需要的睡眠时间多 4 秒,现在这个时间完全不可预测,尤其是当您添加更多变量时比如核心数、可用内存等。
  • 好的,你的意思是 OP 是如何使用它的。我想确保 Sleep() 没有我应该知道的一些奇怪的行为。
【解决方案2】:

这样的事情怎么样。 ——当然,如果 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");
        }
     }
  }

【讨论】:

    猜你喜欢
    • 2015-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-10
    • 2020-04-30
    • 2012-07-07
    相关资源
    最近更新 更多