递归和Rx并行

时间:2015-01-22 13:40:42

标签: c# multithreading performance parallel-processing system.reactive

在尝试有效地遍历目录树时,我尝试了一个描述here的RX解决方案。虽然此解决方案适用于小树深度,但它不适用于大树深度。默认调度程序创建了太多线程,从而减慢了树遍历的速度。

这是我使用的代码:

public static void TestTreeTraversal()
    {
        Func<DirectoryInfo, IObservable<DirectoryInfo>> recurse = null;
        recurse = i => Observable.Return(i)
                        .Concat(i.GetDirInfos().ToObservable().SelectMany(d => recurse(d)))
                        .ObserveOn(Scheduler.Default);
        var obs = recurse(new DirectoryInfo(@"C:\"));
        var result = obs.ToEnumerable().ToList();
    }

public static IEnumerable<DirectoryInfo> GetDirInfos(this DirectoryInfo dir)
    {
        IEnumerable<DirectoryInfo> dirs = null;
        try
        {
            dirs = dir.EnumerateDirectories("*", SearchOption.TopDirectoryOnly);
        }
        catch (Exception)
        {
            yield break;
        }
        foreach (DirectoryInfo d in dirs)
            yield return d;
    }

如果删除ObserveOn(Scheduler.Default),则该函数的工作速度与单线程递归函数相同。使用ObserveOn,每次调用SelectMany时都会创建一个线程,从而大大减慢了这个过程。

有没有办法控制/限制Scheduler可以同时使用的最大线程数?

是否有另一种方法可以使用Rx编写这样的并行树遍历,而不会陷入这种并行陷阱?

1 个答案:

答案 0 :(得分:1)

可以使用this overload of the Merge operator在Rx中完成,也可以将Environment.ProcessorCount传递给maxConcurrent参数。

但是,Rx旨在通过IObservable<T>进行本机异步处理。当然,您可以将IEnumerable<T>转换为IObservable<T>并将其并行处理,就像您在此处所做的那样,但它与Rx中的粒度相反。

此问题的更自然的解决方案是PLINQ,它以IEnumerable<T>开头,用于将查询分区为并行进程,隐式考虑可用的物理处理器数量。

Rx主要是关于驯服并发,而PLINQ主要是关于引入它。

<强>未测试

Func<DirectoryInfo, ParallelQuery<DirectoryInfo>> recurse = null;

recurse = dir => new[] { dir }.AsParallel()
  .Concat(dir.GetDirInfos().AsParallel().SelectMany(recurse));

var result = recurse(new DirectoryInfo(@"C:\")).ToList();