我正在尝试实现一个通用文件系统爬虫,例如 - 能够枚举从给定根开始的所有子文件夹。我想使用async / await / Task范例。
以下是我的代码。它有效,但我怀疑它可以改进。特别是,带注释的CAST()
会在深层目录树中导致不必要的等待,因为循环在每个树级别等待,而不是立即继续处理添加到Task.WaitAll
的新文件夹。
不知何故,我想在folderQueue
等待的任务列表中添加要添加到folderQueue
的新文件夹,而 Task.WaitAll()
是进行中。这甚至可能吗?
WaitAll
答案 0 :(得分:12)
理论上,async / await应该能够在这里提供帮助。在实践中,没有那么多。这是因为Win32不公开目录函数(或某些文件函数,例如打开文件)的异步API。
此外,使用多个线程(Task.Run
)并行化磁盘访问会产生适得其反的趋势,特别是对于传统(非SSD)磁盘。并行文件系统访问(与串行文件系统访问相反)往往会导致磁盘抖动,降低整体吞吐量。
因此,在一般情况下,我建议只使用阻塞目录枚举方法。例如:
class FileSystemCrawlerSO
{
static void Main(string[] args)
{
var numFolders = 0;
Stopwatch watch = new Stopwatch();
watch.Start();
foreach (var dir in Directory.EnumerateDirectories(@"d:\www", "*", SearchOption.AllDirectories))
{
// Do something with the current folder
// e.g. Console.WriteLine($"{dir.FullName}");
++numFolders;
}
watch.Stop();
Console.WriteLine($"Collected {numFolders:N0} folders in {watch.ElapsedMilliseconds} milliseconds.");
if (Debugger.IsAttached)
Console.ReadKey();
}
}
以简单的方式执行此操作的一个不错的副作用是文件夹计数器变量(NumFolders
)不再存在竞争条件。
对于控制台应用,您只需要做。如果要将其放入UI应用程序并且您不想阻止UI线程,那么单个 Task.Run
就足够了。
答案 1 :(得分:5)
这是我的建议。我使用通用Math.Sqrt()
类,所以我不必自己处理锁(虽然这不会自动提高性能)。
然后我为每个文件夹启动一个任务,并在Concurrent*<>
中排队。在开始第一个任务后,我总是等待包中的第一个任务,如果没有其他任务可以等待,我就完成了。
ConcurrentBag<Task>
如果这比你的解决方案更快,我还没有测量。但我认为(正如Yacoub Massad所说),瓶颈将是IO系统本身,而不是你组织任务的方式。
答案 2 :(得分:3)
尝试使用生产者 - 消费者模式。
这是一种在一个线程中抓取目录并在另一个线程中处理的方法。
public class Program
{
private readonly BlockingCollection<DirectoryInfo> collection = new BlockingCollection<DirectoryInfo>();
public void Run()
{
Task.Factory.StartNew(() => CollectFolders(@"d:\www"));
foreach (var dir in collection.GetConsumingEnumerable())
{
// Do something with the current folder
// e.g. Console.WriteLine($"{dir.FullName}");
}
}
public void CollectFolders(string path)
{
try
{
foreach (var dir in new DirectoryInfo(path).EnumerateDirectories("*", SearchOption.AllDirectories))
{
collection.Add(dir);
}
}
finally
{
collection.CompleteAdding();
}
}
}
如果处理速度低于抓取速度,您可能需要使用 Parallel.ForEach 。
Parallel.ForEach(collection.GetConsumingEnumerable(), dir =>
{
// Do something with the current folder
// e.g. Console.WriteLine($"{dir.FullName}");
});