C# 递归与Rx并行
在尝试有效地遍历目录树的同时,我尝试了一个描述的RX解决方案。虽然此解决方案适用于小树深度,但不适用于大树深度。默认调度程序创建的线程太多,减慢了树的遍历速度 以下是我使用的代码:C# 递归与Rx并行,c#,multithreading,performance,parallel-processing,system.reactive,C#,Multithreading,Performance,Parallel Processing,System.reactive,在尝试有效地遍历目录树的同时,我尝试了一个描述的RX解决方案。虽然此解决方案适用于小树深度,但不适用于大树深度。默认调度程序创建的线程太多,减慢了树的遍历速度 以下是我使用的代码: public static void TestTreeTraversal() { Func<DirectoryInfo, IObservable<DirectoryInfo>> recurse = null; recurse = i => Obse
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;
}
publicstaticvoidtesttreetraversal()
{
Func recurse=null;
递归=i=>Observable.Return(i)
.Concat(i.GetDirInfos().ToObservable().SelectMany(d=>recurse(d)))
.ObserveOn(Scheduler.Default);
var obs=recurse(新目录信息(@“C:\”);
var result=obs.ToEnumerable().ToList();
}
公共静态IEnumerable GetDirInfos(此目录信息目录)
{
IEnumerable dirs=null;
尝试
{
dirs=dir.EnumerateDirectory(“*”,SearchOption.TopDirectoryOnly);
}
捕获(例外)
{
屈服断裂;
}
foreach(目录中的目录信息d)
收益率d;
}
如果删除ObserveOn(Scheduler.Default),该函数的工作速度与单线程递归函数相同。使用ObserveOn,似乎每次调用SelectMany时都会创建一个线程,这大大降低了进程的速度
是否有方法控制/限制调度程序可同时使用的最大线程数
有没有另一种方法可以用Rx编写这样的并行树遍历,而不会陷入这种并行陷阱 它可以在Rx中通过将
Environment.ProcessorCount
传递到maxConcurrent
参数来完成
但是,Rx设计用于在本地异步处理的IObservable
上工作。当然,您可以将IEnumerable
转换为IObservable
并并行处理它,就像您在这里所做的那样,但这与Rx中的粒度背道而驰
这个问题的一个更自然的解决方案是,它从一个IEnumerable
开始,设计用于将查询划分为并行进程,隐式地考虑可用的物理处理器的数量
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();
Func recurse=null;
recurse=dir=>new[]{dir}.AsParallel()
.Concat(dir.GetDirInfos().AsParallel().SelectMany(recurse));
var result=recurse(新目录信息(@“C:\”)).ToList();
我已将GetDirInfos翻译成一个可观察的版本,并尝试使用Merge(8)。同样的问题也会发生:创建的线程太多。这样调用:Concat(i.getobservedirinfos().Select(d=>recurse(d)).Merge(8)).ObserveOn(Scheduler.Default);您未经测试的代码不起作用。我已经有了一个使用GetConsumingPartitioner()扩展名在BlockingCollection上使用parallel.ForEach的递归并行版本。我希望得到的是相同的结果,但是使用反应式扩展。您是如何将GetDirInfos
转换为可观察的?您是否在利用本机文件I/O异步?如果没有,那么我建议使用PLINQ仍然有效。如果是这样,那么您的问题是您正在递归地应用Merge
。Rx不限制对Merge
的调用之间的并发性。您只需将Merge
应用于查询的结尾。请记住,即使使用Merge
也需要您自己制定分区策略。也许这就是困惑。您不能只将Merge
插入到现有查询中。该查询专门为每个目录引入并发性。这就是为什么PLINQ更合适的原因。我在Concat()方法中使用Merge,因为Concat()将IObservable作为参数。我知道递归是问题的根源,在SelectMany()或Merge()方法中创建了多个线程。我还知道还有其他方法可以进行并行树遍历(我重复一遍,我已经有了一个可行的方法)。我要问的是:“有没有另一种方法可以用Rx编写这样的并行树遍历”SelectMany
和Merge
都没有引入并发性。ObserveOn
操作符是唯一引入并发性的操作符,它对每个目录都这样做。这就是您选择使用的查询。如果您的问题是Rx是否可以为您划分序列,那么答案是否定的。您必须自己选择要并行执行的递归部分。