Procedura: scorrere le directory dei file con PLINQ

Questo articolo mostra due modi per parallelizzare le operazioni sulle directory di file. La prima query usa il metodo GetFiles per popolare una matrice di nomi di file in una directory e in tutte le sottodirectory. Questo metodo può introdurre latenza all'inizio dell'operazione, perché non esegue la restituzione finché non viene popolata l'intera matrice. Tuttavia, una volta popolata, PLINQ può elaborarla in parallelo molto rapidamente.

La seconda query usa i metodi statici EnumerateDirectories e EnumerateFiles, che iniziano a restituire risultati immediatamente. Questo approccio può rivelarsi più veloce quando si esegue l'iterazione di alberi di directory di grandi dimensioni, anche se i tempi di elaborazione rispetto al primo esempio possono dipendere da molti fattori.

Nota

Lo scopo di questi esempi è mostrarne l'utilizzo e la loro esecuzione potrebbe non essere più rapida della query LINQ to Objects sequenziale equivalente. Per altre informazioni sull'aumento di velocità, vedere Informazioni sull'aumento di velocità in PLINQ.

Esempio di GetFiles

L'esempio seguente mostra come eseguire l'iterazione di directory di file in semplici scenari quando si ha accesso a tutte le directory nell'albero, le dimensioni dei file non sono molto grandi e i tempi di accesso non sono significativi. Questo approccio comporta un periodo di latenza all'inizio, durante la creazione della matrice di nomi di file.


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

Esempio di EnumerateFiles

L'esempio seguente mostra come eseguire l'iterazione di directory di file in semplici scenari quando si ha accesso a tutte le directory nell'albero, le dimensioni dei file non sono molto grandi e i tempi di accesso non sono significativi. Questo approccio inizia a restituire risultati più rapidamente rispetto all'esempio precedente.

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

Quando si usa GetFiles, assicurarsi di avere autorizzazioni sufficienti per tutte le directory nell'albero. In caso contrario, verrà generata un'eccezione e non sarà restituito alcun risultato. Quando si usa EnumerateDirectories in una query PLINQ, risulta problematico gestire le eccezioni di I/O in un modo appropriato che permetta di proseguire con l'iterazione. Se il codice deve gestire eccezioni di I/O o di accesso non autorizzato, è consigliabile provare l'approccio descritto in Procedura: Eseguire l'iterazione di file di directory con la classe Parallel.

Se la latenza di I/O costituisce un problema, ad esempio con I/O di file in una rete, provare a usare una delle tecniche di I/O asincrono descritte in TPL e programmazione asincrona .NET tradizionale e in questo post di blog.

Vedi anche