如何:使用 PLINQ 循环访问文件目录

更新:2010 年 5 月

此示例演示对文件目录执行并行操作的两种简单方式。 第一个查询使用 GetFiles 方法填充目录以及所有子目录中文件名的数组。 此方法在填充整个数组之前不返回,因此,可能会在操作开始导致延迟。 但是,填充数组后,PLINQ 可以非常快地并行处理它。

第二个查询使用静态 EnumerateDirectoriesEnumerateFiles 方法,这两个方法立即开始返回结果。 循环访问大型目录树时,此方法可能更快,但是相对于第一个示例处理时间可能取决于许多因素。

警告说明警告

这些示例旨在演示用法,运行速度可能不如等效的顺序 LINQ to Objects 查询快。有关加速的更多信息,请参见了解 PLINQ 中的加速

示例

下面的示例演示如何在下面这种简单的情况中循环访问文件目录:您有权访问树中的所有目录,文件大小不太大,并且访问时间不太多。 此方法在最开始构造文件名数组时有一段时间的延迟。


struct FileResult
{
    public string Text;
    public string FileName;
}
// Use Directory.GetFiles to get the source sequence of file names.
public static void FileIteration_1(string path)
{       
    var sw = Stopwatch.StartNew();
    int count = 0;
    string[] files = null;
    try
    {
        files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
    }
    catch (UnauthorizedAccessException e)
    {
        Console.WriteLine("You do not have permission to access one or more folders in this directory tree.");
        return;
    }

    catch (FileNotFoundException)
    {
        Console.WriteLine("The specified directory {0} was not found.", path);
    }

    var fileContents = from file in files.AsParallel()
            let extension = Path.GetExtension(file)
            where extension == ".txt" || extension == ".htm"
            let text = File.ReadAllText(file)
            select new FileResult { Text = text , FileName = file }; //Or ReadAllBytes, ReadAllLines, etc.              

    try
    {
        foreach (var item in fileContents)
        {
            Console.WriteLine(Path.GetFileName(item.FileName) + ":" + item.Text.Length);
            count++;
        }
    }
    catch (AggregateException ae)
    {
        ae.Handle((ex) =>
            {
                if (ex is UnauthorizedAccessException)
                {
                   Console.WriteLine(ex.Message);
                   return true;
                }
                return false;
            });
    }

    Console.WriteLine("FileIteration_1 processed {0} files in {1} milliseconds", count, sw.ElapsedMilliseconds);
    }

下面的示例演示如何在下面这种简单的情况中循环访问文件目录:您有权访问树中的所有目录,文件大小不太大,并且访问时间不太多。 此方法比前面的示例更快地开始生成结果。


struct FileResult
{
    public string Text;
    public string FileName;
}

// Use Directory.EnumerateDirectories and EnumerateFiles to get the source sequence of file names.
public static void FileIteration_2(string path) //225512 ms
{
    var count = 0;
    var sw = Stopwatch.StartNew();
    var fileNames = from dir in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)
                    select dir;


    var fileContents = from file in fileNames.AsParallel() // Use AsOrdered to preserve source ordering
                       let extension = Path.GetExtension(file)
                       where extension == ".txt" || extension == ".htm"
                       let Text = File.ReadAllText(file)
                       select new { Text, FileName = file }; //Or ReadAllBytes, ReadAllLines, etc.
    try
    {
        foreach (var item in fileContents)
        {
            Console.WriteLine(Path.GetFileName(item.FileName) + ":" + item.Text.Length);
            count++;
        }
    }
    catch (AggregateException ae)
    {
        ae.Handle((ex) =>
            {
                if (ex is UnauthorizedAccessException)
                {
                   Console.WriteLine(ex.Message);
                   return true;
                }
                return false;
            });
    }

    Console.WriteLine("FileIteration_2 processed {0} files in {1} milliseconds", count, sw.ElapsedMilliseconds);
}

使用 GetFiles 时,请确保您对树中的所有目录具有足够的权限。 否则,将引发一个异常并且不会返回任何结果。 在 PLINQ 查询中使用 EnumerateDirectories 时,以允许您继续循环访问的正常方式处理 I/O 异常存在问题。 如果您的代码必须处理 I/O 或未授权访问异常,则应考虑如何:使用并行类循环访问文件目录中介绍的方法。

如果 I/O 延迟成为问题,例如通过网络的文件 I/O,请考虑使用 TPL 和传统 .NET 异步编程和此博客文章中介绍的某种异步 I/O 技巧。

请参见

概念

并行 LINQ (PLINQ)

修订记录

日期

修订记录

原因

2010 年 5 月

添加了有关用法与 加速的注释。

客户反馈