使用英语阅读

通过


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

本文展示了两种方法,以对文件目录平行执行操作。 第一个查询使用 GetFiles 方法,在数组中填充目录和所有子目录中的文件名。 在整个数组填充完成前,此方法不会返回数组,所以可能会在操作开始时引入延迟。 不过,在填充数组后,PLINQ 可以快速地并行处理数组。

第二个查询使用立即开始返回结果的静态 EnumerateDirectoriesEnumerateFiles 方法。 循环访问大型目录树时,这种方法可能会比第一个示例更快,不过处理时间取决于很多因素。

备注

这些示例用于演示用法,可能不会比相当的顺序 LINQ to Objects 查询快。 若要详细了解加速,请参阅了解 PLINQ 中的加速

GetFiles 示例

此示例展示了如何在以下简单方案中循环访问文件目录:有权访问树中的所有目录,文件大小不大,访问时间也不是很长。 这种方法在最初构造文件名数组时有一段延迟。


// Use Directory.GetFiles to get the source sequence of file names.
public static void FileIterationOne(string path)
{
    var sw = Stopwatch.StartNew();
    int count = 0;
    string[]? files = null;
    try
    {
        files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
    }
    catch (UnauthorizedAccessException)
    {
        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 {path} was not found.");
    }

    var fileContents =
        from FileName in files?.AsParallel()
        let extension = Path.GetExtension(FileName)
        where extension == ".txt" || extension == ".htm"
        let Text = File.ReadAllText(FileName)
        select new
        {
            Text,
            FileName
        };

    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 uae)
            {
                Console.WriteLine(uae.Message);
                return true;
            }
            return false;
        });
    }

    Console.WriteLine($"FileIterationOne processed {count} files in {sw.ElapsedMilliseconds} milliseconds");
}

EnumerateFiles 示例

此示例展示了如何在以下简单方案中循环访问文件目录:有权访问树中的所有目录,文件大小不大,访问时间也不是很长。 这种方法比上一示例更快地开始生成结果。

public static void FileIterationTwo(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 FileName in fileNames.AsParallel()
        let extension = Path.GetExtension(FileName)
        where extension == ".txt" || extension == ".htm"
        let Text = File.ReadAllText(FileName)
        select new
        {
            Text,
            FileName
        };
    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 uae)
            {
                Console.WriteLine(uae.Message);
                return true;
            }
            return false;
        });
    }

    Console.WriteLine($"FileIterationTwo processed {count} files in {sw.ElapsedMilliseconds} milliseconds");
}

使用 GetFiles 时,请确保有权访问树中的所有目录。 否则,将引发异常,且不会返回任何结果。 如果在 PLINQ 查询中使用 EnumerateDirectories,棘手的是合理处理 I/O 异常,以便能够继续循环访问。 如果代码必须处理 I/O 或未经授权的访问异常,应考虑使用如何:使用并行类循环访问文件目录中介绍的方法。

如果 I/O 延迟造成问题(例如,对于通过网络的文件 I/O),请考虑使用 TPL 和传统 .NET 异步编程和这篇博客文章中介绍的某种异步 I/O 方法。

请参阅