Поделиться через


Практическое руководство. Перебор каталогов с файлами с помощью PLINQ

В этом примере показано два простых способа параллельного выполнения операций в каталогах с файлами. Первый запрос использует метод GetFiles для заполнения массива имен файлов в каталоге и всех подкаталогах. Этот метод не возвращает результат, пока весь массив не будет заполнен, поэтому он может вызывать задержку в начале операции. Однако после заполнения массива PLINQ может очень быстро выполнять параллельную обработку.

Второй запрос использует статические методы EnumerateDirectories и EnumerateFiles, которые сразу же начинают возвращать результаты. Благодаря такому подходу скорость обработки может быть выше при переборе по большим деревьям каталогов, хотя время обработки в сравнении с первым примером может зависеть от многих факторов.

Предупреждающее замечаниеВнимание

Эти примеры демонстрируют использование и могут выполняться медленнее, чем аналогичный последовательный запрос 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 следует убедиться, что имеются соответствующие разрешения для всех каталогов в дереве. В противном случае возникнет исключение и результаты возвращены не будут. При использовании метода EnumerateDirectories в запросе PLINQ довольно трудно обрабатывать исключения ввода-вывода удобным способом, при котором можно продолжать перебор. Если код должен обрабатывать исключения ввода-вывода или несанкционированного доступа, необходимо обратить внимание на подход, рассмотренный в разделе Практическое руководство. Перебор каталогов с файлами с помощью параллельного класса.

Если задержка ввода-вывода недопустима (например, при передаче сигналов ввода-вывода по сети), рассмотрите возможность использования методики асинхронного ввода-вывода, описанной в разделе Библиотека параллельных задач и традиционное асинхронное программирование .NET и в этой записи блога.

См. также

Основные понятия

Parallel LINQ (PLINQ)

Журнал изменений

Дата

Журнал

Причина

Май 2010

Добавлено примечание о сравнении использования и ускорения.

Обратная связь от клиента.