Condividi tramite


Procedura: scorrere le directory dei file con la classe Parallel

In molti casi, l'iterazione di file è un'operazione che può essere facilmente parallelizzata. Nell'argomento Procedura: scorrere le directory dei file con PLINQ viene illustrato il modo più semplice per eseguire questa attività per molti scenari. Quando tuttavia il codice deve gestire i molti tipi di eccezioni che possono essere generate durante l'accesso al file system, è possibile che si verifichino alcuni problemi. Nell'esempio seguente viene illustrato un possibile approccio al problema. Per l'attraversamento di tutti i file e tutte le cartelle in una directory specifica viene utilizzata un'iterazione basata su stack, consentendo al codice di intercettare e gestire diverse eccezioni. Naturalmente, la modalità di gestione delle eccezioni compete all'utente.

Esempio

Nell'esempio seguente l'iterazione nelle directory viene eseguita in sequenza, ma l'elaborazione dei file viene effettuata in parallelo. Questo è probabilmente il migliore approccio nel caso di un rapporto elevato tra file e directory. È inoltre possibile parallelizzare l'iterazione delle directory e accedere a ogni file in sequenza. La parallelizzazione di entrambi i cicli non è probabilmente una scelta efficiente se la destinazione non è un computer specifico con un numero elevato di processori. Come sempre, è tuttavia consigliabile testare l'applicazione completamente per determinare quale sia l'approccio migliore.

Imports System
Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.IO
Imports System.Linq
Imports System.Text
Imports System.Threading
Imports System.Threading.Tasks

Module Parallel_File
    Sub Main(ByVal args() As String)
        TraverseTreeParallelForEach("C:\Program Files", Sub(f)

                                                            ' For this demo we don't do anything with the data
                                                            ' except to read it.
                                                            Dim data() As Byte = File.ReadAllBytes(f)

                                                            ' For user interest, although it slows down the operation.
                                                            Console.WriteLine(f)
                                                        End Sub)

        ' Keep the console window open.
        Console.ReadKey()
    End Sub



    Public Sub TraverseTreeParallelForEach(ByVal root As String, ByVal action As Action(Of String))


        'Count of files traversed and timer for diagnostic output
        Dim fileCount As Integer = 0
        Dim sw As Stopwatch = Stopwatch.StartNew()

        ' Use this value to determine whether to parallelize
        ' file processing on each folder.
        Dim procCount As Integer = System.Environment.ProcessorCount

        ' Data structure to hold names of subfolders to be
        ' examined for files.
        Dim dirs As Stack(Of String) = New Stack(Of String)

        If System.IO.Directory.Exists(root) = False Then

            Throw New ArgumentException()
        End If
        dirs.Push(root)

        While (dirs.Count > 0)

            Dim currentDir As String = dirs.Pop()
            Dim subDirs() As String = Nothing
            Dim files() As String = Nothing

            Try
                subDirs = System.IO.Directory.GetDirectories(currentDir)
                ' An UnauthorizedAccessException exception will be thrown if we do not have
                ' discovery permission on a folder or file. It may or may not be acceptable 
                ' to ignore the exception and continue enumerating the remaining files and 
                ' folders. It is also possible (but unlikely) that a DirectoryNotFound exception 
                ' will be raised. This will happen if currentDir has been deleted by
                ' another application or thread after our call to Directory.Exists. The 
                ' choice of which exceptions to catch depends entirely on the specific task 
                ' you are intending to perform and also on how much you know with certainty 
                ' about the systems on which this code will run.
            Catch e As UnauthorizedAccessException

                Console.WriteLine(e.Message)
                Continue While

            Catch e As System.IO.DirectoryNotFoundException

                Console.WriteLine(e.Message)
                Continue While
            End Try

            Try
                files = System.IO.Directory.GetFiles(currentDir)
            Catch e As UnauthorizedAccessException

                Console.WriteLine(e.Message)
                Continue While


            Catch e As System.IO.DirectoryNotFoundException

                Console.WriteLine(e.Message)
                Continue While
            End Try

            ' Perform the required action on each file here in parallel
            ' if there are a sufficient number of files in the directory
            ' or else sequentially if not. Files are opened and processed
            ' synchronously but this could be modified to perform async I/O.
            Try

                If files.Length < procCount Then

                    For Each file In files

                        action(file)
                        fileCount = fileCount + 1
                    Next
                Else
                    Parallel.ForEach(files, Function() 0, Function(file, loopState, localCount)
                                                              action(file)
                                                              localCount = localCount + 1
                                                              Return CType(localCount, Integer)
                                                          End Function,
                    Sub(c)
                        Interlocked.Exchange(fileCount, fileCount + c)
                    End Sub)
                End If
            Catch ae As AggregateException
                ae.Handle(Function(ex)

                              If TypeOf (ex) Is UnauthorizedAccessException Then

                                  ' Here we just output a message and go on.
                                  Console.WriteLine(ex.Message)
                                  Return True
                              End If
                              ' Handle other exceptions here if necessary...

                              Return False
                          End Function)
            End Try
            ' Push the subdirectories onto the stack for traversal.
            ' This could also be done before handing the files.
            For Each str As String In subDirs
                dirs.Push(str)
            Next

            ' For diagnostic purposes.
            Console.WriteLine("Processed {0}  files in {1}  milleseconds", fileCount, sw.ElapsedMilliseconds)
        End While
End Sub
End Module
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Parallel_File
{
    class Program
    {

        static void Main(string[] args)
        {            

            TraverseTreeParallelForEach(@"C:\Program Files", (f) =>
            {
                // For this demo we don't do anything with the data
                // except to read it.
                byte[] data = File.ReadAllBytes(f);

                // For user interest, although it slows down the operation.
                Console.WriteLine(f);
            });

            // Keep the console window open.
            Console.ReadKey();
        }



        public static void TraverseTreeParallelForEach(string root, Action<string> action)
        {

            //Count of files traversed and timer for diagnostic output
            int fileCount = 0;
            var sw = Stopwatch.StartNew();

            // Use this value to determine whether to parallelize
            // file processing on each folder.
            int procCount = System.Environment.ProcessorCount;

            // Data structure to hold names of subfolders to be
            // examined for files.
            Stack<string> dirs = new Stack<string>();

            if (!System.IO.Directory.Exists(root))
            {
                throw new ArgumentException();
            }
            dirs.Push(root);

            while (dirs.Count > 0)
            {
                string currentDir = dirs.Pop();
                string[] subDirs = null;
                string[] files = null;

                try
                {
                    subDirs = System.IO.Directory.GetDirectories(currentDir);
                }
                // An UnauthorizedAccessException exception will be thrown if we do not have
                // discovery permission on a folder or file. It may or may not be acceptable 
                // to ignore the exception and continue enumerating the remaining files and 
                // folders. It is also possible (but unlikely) that a DirectoryNotFound exception 
                // will be raised. This will happen if currentDir has been deleted by
                // another application or thread after our call to Directory.Exists. The 
                // choice of which exceptions to catch depends entirely on the specific task 
                // you are intending to perform and also on how much you know with certainty 
                // about the systems on which this code will run.
                catch (UnauthorizedAccessException e)
                {
                    Console.WriteLine(e.Message);
                    continue;
                }
                catch (System.IO.DirectoryNotFoundException e)
                {
                    Console.WriteLine(e.Message);
                    continue;
                }

                try
                {
                    files = System.IO.Directory.GetFiles(currentDir);
                }

                catch (UnauthorizedAccessException e)
                {
                    Console.WriteLine(e.Message);
                    continue;
                }

                catch (System.IO.DirectoryNotFoundException e)
                {
                    Console.WriteLine(e.Message);
                    continue;
                }

                // Perform the required action on each file here in parallel
                // if there are a sufficient number of files in the directory
                // or else sequentially if not. Files are opened and processed
                // synchronously but this could be modified to perform async I/O.
                try
                {
                    if (files.Length < procCount)
                    {
                        foreach (var file in files)
                        {
                            action(file);
                            fileCount++;                            
                        }
                    }
                    else
                    {

                        Parallel.ForEach(files, () => 0, (file, loopState, localCount) =>
                        {
                            action(file);
                            return (int) ++localCount;

                        },
                        (c) =>
                        {
                            Interlocked.Exchange(ref fileCount, fileCount + c);                          
                        });
                    }
                }
                catch (AggregateException ae)
                {
                    ae.Handle((ex) =>
                        {
                            if (ex is UnauthorizedAccessException) 
                            {
                                // Here we just output a message and go on.
                                Console.WriteLine(ex.Message);
                                return true;
                            }
                            // Handle other exceptions here if necessary...

                            return false;
                        });
                }

                // Push the subdirectories onto the stack for traversal.
                // This could also be done before handing the files.
                foreach (string str in subDirs)
                    dirs.Push(str);
            }

            // For diagnostic purposes.
            Console.WriteLine("Processed {0} files in {1} milleseconds", fileCount, sw.ElapsedMilliseconds);
        }
    }
}

In questo esempio le operazioni di I/O dei file vengono eseguite in modo sincrono. Nel caso di file di grandi dimensioni o di connessioni di rete lente, potrebbe essere preferibile accedere ai file in modo asincrono. L'iterazione parallela può essere combinata con tecniche di I/O asincrono. Per ulteriori informazioni, vedere Task Parallel Library e programmazione asincrona .NET tradizionale.

Si noti che, se viene generata un'eccezione nel thread principale, i thread avviati dal metodo ForEach possono rimanere in esecuzione. Per arrestare tali thread, è possibile impostare una variabile booleana nei gestori di eccezioni e verificarne il valore in ogni iterazione del ciclo parallelo. Se il valore indica che è stata generata un'eccezione, utilizzare la variabile ParallelLoopState per uscire dal ciclo o arrestarlo. Per ulteriori informazioni, vedere Procedura: arrestare o interrompere un ciclo Parallel.For.

Vedere anche

Concetti

Parallelismo dei dati (Task Parallel Library)