Практическое руководство. Перебор каталогов с файлами с помощью PLINQHow to: Iterate File Directories with PLINQ

В этом примере показаны два простых способа параллельного выполнения операций с каталогами файлов.This example shows two simple ways to parallelize operations on file directories. Первый запрос использует метод GetFiles, чтобы заполнить массив имен всех файлов и подкаталогов в каталоге.The first query uses the GetFiles method to populate an array of file names in a directory and all subdirectories. Этот метод не возвращает результаты, пока не заполнит весь массив, поэтому в начале его работы можно ожидать существенную задержку.This method does not return until the entire array is populated, and therefore it can introduce latency at the beginning of the operation. Но после заполнения массива PLINQ сможет очень быстро обрабатывать его параллельно.However, after the array is populated, PLINQ can process it in parallel very quickly.

Второй запрос использует статические методы EnumerateDirectories и EnumerateFiles, которые сразу начинают возвращать результаты.The second query uses the static EnumerateDirectories and EnumerateFiles methods which begin returning results immediately. Этот подход может оказаться быстрее, если вы просматриваете большие деревья каталогов, но разница с первым примером по времени обработки может зависеть от многих факторов.This approach can be faster when you are iterating over large directory trees, although the processing time compared to the first example can depend on many factors.

Предупреждение

Этот пример предназначен только для демонстрации использования, и может выполняться не быстрее аналогичного последовательного запроса LINQ to Objects.These examples are intended to demonstrate usage, and might not run faster than the equivalent sequential LINQ to Objects query. См. дополнительные сведения об ускорении выполнения в PLINQ.For more information about speedup, see Understanding Speedup in PLINQ.

ПримерExample

В следующем примере показан итеративный обход каталогов с файлами для простых сценариев, когда в дереве есть доступ ко всем каталогам, нет больших файлов и существенных задержек при доступе к ним.The following example shows how to iterate over file directories in simple scenarios when you have access to all directories in the tree, the file sizes are not very large, and the access times are not significant. При таком подходе предполагается задержка в начале, пока код заполняет массив имен файлов.This approach involves a period of latency at the beginning while the array of file names is being constructed.


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);
    }

ПримерExample

В следующем примере показан итеративный обход каталогов с файлами для простых сценариев, когда в дереве есть доступ ко всем каталогам, нет больших файлов и существенных задержек при доступе к ним.The following example shows how to iterate over file directories in simple scenarios when you have access to all directories in the tree, the file sizes are not very large, and the access times are not significant. При таком подходе результаты возвращаются гораздо быстрее, чем в предыдущем примере.This approach begins producing results faster than the previous example.


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, следите за наличием нужных разрешений для всех каталогов в дереве.When using GetFiles, be sure that you have sufficient permissions on all directories in the tree. В противном случае создается исключение и результаты не возвращаются.Otherwise an exception will be thrown and no results will be returned. При использовании EnumerateDirectories в запросе PLINQ довольно трудно обрабатывать исключения ввода-вывода настолько мягко, чтобы можно было продолжить работу.When using the EnumerateDirectories in a PLINQ query, it is problematic to handle I/O exceptions in a graceful way that enables you to continue iterating. Если код должен обрабатывать исключения с ошибками ввода-вывода или несанкционированного доступа, то мы рекомендуем применить другой подход, описанный в статье Практическое руководство. Перебор каталогов с файлами с помощью параллельного класса.If your code must handle I/O or unauthorized access exceptions, then you should consider the approach described in How to: Iterate File Directories with the Parallel Class.

Если есть риск задержки при операциях ввода-вывода (например, при обращении к файлу по сети), попробуйте один из асинхронных подходов, которые описаны в статье Библиотека параллельных задач и традиционное асинхронное программирование .NET Framework и в этой записи блога.If I/O latency is an issue, for example with file I/O over a network, consider using one of the asynchronous I/O techniques described in TPL and Traditional .NET Framework Asynchronous Programming and in this blog post.

См. такжеSee also