【问题标题】:Spawning worker threads产生工作线程
【发布时间】:2011-02-11 04:47:42
【问题描述】:

在 C# 中,

如何产生多个线程,然后在返回整个结果集之前按顺序将结果添加到列表中?

有哪些最佳做法?

到目前为止,我一直在使用 ManualResetEvent 来发出最后一个元素已被线程处理的信号。

但是当它返回时,我需要让它们按顺序合并结果集,这样我们就不会遇到返回值列表(总结果)的争用问题。

【问题讨论】:

    标签: c# multithreading .net-3.5


    【解决方案1】:

    当您启动每个线程时,将您的序列标识符和回调方法传递给它,并增加一个指示正在运行的线程总数的计数器。当每个线程完成时,它会调用回调方法,该方法会减少正在运行的线程计数并将结果插入到SortedDictionary 中,并以序列 ID 为键。当最后一个线程完成时,回调可以向您的主程序发出信号。

    【讨论】:

      【解决方案2】:

      如果您使用的是 .Net 4,例如,您可以使用 Task 类。这是一个合并列表的示例

      Task<List<string>> task1 = new Task<List<string>>(SomeFunction);
      Task<List<string>> task2 = new Task<List<string>>(SomeFunction);
      task1.Start();
      task2.Start();
      
      var taskList = new List<Task<List<string>>> {task1, task2};
      
      Task.WaitAll(taskList.ToArray());
      
      List<string> res = new List<string>();
      foreach (Task<List<string>> t in taskList)
      {
          res.AddRange(t.Result);
      }
      

      和你的功能

      List<string> SomeFunction()
      {
          return new List<string>{"1","2"};
      }
      

      【讨论】:

        【解决方案3】:

        现在是Reactive Extensions for .NET Framework 的一部分的任务并行库使这样的事情变得微不足道。有一组 Parallel 构造用于并行化您的代码,还有一组线程安全的 Concurrent{Container}s 可以与它​​们一起使用。

        以下是对一组数字进行平方的示例,使用Parallel.ForConcurrentBag 来存储结果。

        using System.Threading.Tasks;
        using System.Collections.Concurrent;
        
        namespace ParallelTest
        {
            class Program
            {
                static void Main(string[] args)
                {
                    var results = new ConcurrentBag<int>();
                    Parallel.For(0, 10, i =>
                    {
                        results.Add(i * i);
                    });
                    foreach (int i in results)
                        System.Console.WriteLine(i);
                }
            }
        }
        

        ConcurrentBag 是常规的 IEnumerable,正如您所见,我使用常规的非并行 foreach 来打印最后的结果。

        注意:所有这些东西在 .NET 4.0 中实际上都是标准的,如果你想要在 .NET 3.5 中使用 Rx,你只需要它。

        【讨论】:

        • 我也打算建议 TPL,但我认为它现在是 .NET 框架的一部分,而不是 Rx 扩展,但它是 Parallel FX 扩展 ;)
        • @ntziolis:如果你想要它用于 3.5,你需要 Rx 扩展。它包括 TPL 和 PLINQ 的反向移植。
        • @Reed - 很高兴知道,直到现在(好吧,3.5 中的最后一个项目已经很久以前了)我使用过 TPL 的 CTP,我不知道Rx TPL 实际上是一个 .NET 4 反向端口,谢谢提示!
        【解决方案4】:

        如果您在生成线程之前知道最终顺序(您的“顺序”暗示),您可以将索引传递给每个线程,并将其结果写入数组中的那个“槽”。因此,当所有线程都完成了处理(以任何顺序)时,结果将已经正确排序,完全避免了后处理排序的需要。

        【讨论】:

        • 在获取对插槽数组的引用时,您仍然会遇到争用问题。
        • @LB - 如果每个线程都有一个专用插槽,则不存在争用。数组本身(与其引用的项目相反)在线程运行之前创建,然后销毁,并且在处理期间是不可变的。这些项目由线程创建,添加到数组中,然后由“主”线程使用(处理完成后),因此再次没有争用。显然 .net 4 中有更好的方法......但 OP 指定 .net 3.5
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-20
        • 2019-04-08
        • 2019-12-04
        • 2010-12-05
        • 1970-01-01
        相关资源
        最近更新 更多