异步枚举文件夹

时间:2016-01-03 17:48:01

标签: c# asynchronous filesystems async-await directory

我正在尝试实现一个通用文件系统爬虫,例如 - 能够枚举从给定根开始的所有子文件夹。我想使用async / await / Task范例。

以下是我的代码。它有效,但我怀疑它可以改进。特别是,带注释的CAST()会在深层目录树中导致不必要的等待,因为循环在每个树级别等待,而不是立即继续处理添加到Task.WaitAll的新文件夹。

不知何故,我想在folderQueue 等待的任务列表中添加要添加到folderQueue的新文件夹,而 Task.WaitAll()是进行中。这甚至可能吗?

WaitAll

3 个答案:

答案 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)

单独抓取和处理

尝试使用生产者 - 消费者模式。
这是一种在一个线程中抓取目录并在另一个线程中处理的方法。

Producer-Consumer pattern

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

Producer-Parallel Consumer pattern

Parallel.ForEach(collection.GetConsumingEnumerable(), dir =>
{
    // Do something with the current folder
    // e.g. Console.WriteLine($"{dir.FullName}");
});