在尝试有效地遍历目录树时,我尝试了一个描述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编写这样的并行树遍历,而不会陷入这种并行陷阱?
答案 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();