Como: iterar diretórios de arquivos com PLINQ

Este artigo mostra duas maneiras de paralelizar operações em diretórios de arquivos. A primeira consulta usa o método GetFiles para preencher uma matriz de nomes de arquivo em um diretório e em todos os subdiretórios. Esse método poderá introduzir latência no início da operação, pois ele não retornará até que toda a matriz seja preenchida. No entanto, após o preenchimento da matriz, PLINQ pode processá-la em paralelo rapidamente.

A segunda consulta usa métodos EnumerateDirectories e EnumerateFiles estáticos que começam a retornar os resultados imediatamente. Essa abordagem pode ser mais rápida quando você estiver iterando em árvores de diretório grandes, mas o tempo de processamento em comparação o primeiro exemplo depende de vários fatores.

Observação

Esses exemplos têm como objetivo demonstrar o uso, e talvez não executem tão rápido quanto a consulta LINQ to Objects sequencial equivalente. Para saber mais sobre agilização, confira Noções básicas sobre agilização no PLINQ.

Exemplo de GetFiles

Este exemplo mostra como iterar nos diretórios de arquivos em cenários simples, quando você tem acesso a todos os diretórios na árvore, os tamanhos de arquivos não são grandes e os tempos de acesso não são consideráveis. Essa abordagem envolve um período de latência no início, enquanto a matriz de nomes de arquivo está sendo construída.


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

Exemplo de EnumerateFiles

Este exemplo mostra como iterar nos diretórios de arquivos em cenários simples, quando você tem acesso a todos os diretórios na árvore, os tamanhos de arquivos não são grandes e os tempos de acesso não são consideráveis. Essa abordagem começa a produzir resultados mais rápido do que o exemplo anterior.

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

Ao usar GetFiles, verifique se você tem permissões suficientes em todos os diretórios na árvore. Caso contrário, uma exceção será gerada e nenhum resultado será retornado. Ao usar o EnumerateDirectories em uma consulta PLINQ, é problemático tratar as exceções de E/S de uma maneira que permita que você continue a iteração. Se o seu código precisar manipular exceções de acesso não autorizado ou de E/S, considere a abordagem descrita em Como iterar diretórios de arquivos com a classe paralela.

Se a latência de E/S for um problema, por exemplo com E/S de arquivo em uma rede, considere o uso de uma das técnicas de E/S assíncronas descritas em TPL e programação assíncrona de TPL e .NET e nesta postagem de blog .

Confira também