Procédure : itérer les répertoires de fichiers avec PLINQ

Cet article montre deux manières de paralléliser des opérations sur des répertoires de fichiers. La première requête utilise la méthode GetFiles pour renseigner un tableau de noms de fichiers dans un répertoire et tous ses sous-répertoires. Cette méthode peut introduire une latence au début de l’opération car elle ne retourne aucun résultat tant que le tableau n’est pas entièrement renseigné. En revanche, une fois le tableau renseigné, PLINQ est capable de le traiter en parallèle rapidement.

La seconde requête utilise les méthodes EnumerateDirectories et EnumerateFiles statiques qui commencent à retourner des résultats immédiatement. Cette approche peut s’avérer plus rapide quand vous itérez au sein d’arborescences de répertoires importantes, mais le temps de traitement par rapport au premier exemple dépend de nombreux facteurs.

Notes

Ces exemples, destinés à illustrer l’utilisation, peuvent ne pas s’exécuter plus rapidement que la requête LINQ to Objects séquentielle équivalente. Pour plus d’informations sur l’accélération, consultez Fonctionnement de l’accélération dans PLINQ.

Exemple GetFiles

Cet exemple montre comment itérer au sein de répertoires de fichiers dans des scénarios simples où vous avez accès à tous les répertoires de l’arborescence, où les tailles de fichier ne sont pas grandes et où les temps d’accès ne sont pas significatifs. Cette approche implique une période de latence au début, le temps que le tableau de noms de fichiers soit créé.


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

Exemple EnumerateFiles

Cet exemple montre comment itérer au sein de répertoires de fichiers dans des scénarios simples où vous avez accès à tous les répertoires de l’arborescence, où les tailles de fichier ne sont pas grandes et où les temps d’accès ne sont pas significatifs. Cette méthode commence à générer des résultats plus rapidement que l’exemple précédent.

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

Si vous utilisez GetFiles, assurez-vous de disposer des autorisations suffisantes sur tous les répertoires de l’arborescence. Dans le cas contraire, une exception sera levée et aucun résultat ne sera retourné. Quand vous utilisez la méthode EnumerateDirectories dans une requête PLINQ, la gestion des exceptions d’E/S de façon normale permettant de poursuivre l’itération devient problématique. Si votre code doit gérer les E/S ou les exceptions d’accès non autorisé, vous devez envisager l’approche décrite dans Comment : itérer les répertoires de fichiers avec la classe parallèle.

Si la latence des E/S s’avère un problème, par exemple avec des E/S de fichiers sur un réseau, optez plutôt pour l’une des techniques d’E/S asynchrones décrites dans Bibliothèque parallèle de tâches et programmation asynchrone .NET traditionnelle et dans ce billet de blog.

Voir aussi