Compartir a través de


Cómo: Recorrer en iteración directorios con PLINQ

En este ejemplo se muestran dos maneras sencillas de paralelizar las operaciones en los directorios de archivos. En la primera consulta se usa el método GetFiles para rellenar una matriz de nombres de archivo en un directorio y todos los subdirectorios. Este método no devuelve ningún valor hasta que se rellena la matriz completa y, por consiguiente, puede presentar la latencia al principio de la operación. Sin embargo, una vez que se rellena la matriz, PLINQ puede procesarla en paralelo muy rápidamente.

En la segunda consulta se usan los métodos estáticos EnumerateDirectories y EnumerateFiles, que empiezan a devolver resultados inmediatamente. Este enfoque puede ofrecer mayor velocidad cuando se itera en árboles de directorios grandes, aunque, en comparación con el primer ejemplo, el tiempo de procesamiento puede depender de muchos factores.

Nota de precauciónPrecaución

Estos ejemplos se han diseñado para mostrar el uso y podrían no ejecutarse más rápidamente que la consulta secuencial equivalente de LINQ to Objects.Para obtener más información sobre la aceleración, vea Introducción a la velocidad en PLINQ.

Ejemplo

En el ejemplo siguiente se muestra cómo iterar en los directorios de archivos en escenarios simples cuando se tiene acceso a todos los directorios en el árbol, los tamaños de archivo no son muy grandes y los tiempos de acceso no son significativos. Este enfoque implica un período de latencia al principio mientras la matriz de nombres de archivo se construye.


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

En el ejemplo siguiente se muestra cómo iterar en los directorios de archivos en escenarios simples cuando se tiene acceso a todos los directorios en el árbol, los tamaños de archivo no son muy grandes y los tiempos de acceso no son significativos. Este enfoque empieza a producir resultados más rápidamente que el ejemplo anterior.


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

Al usar GetFiles, asegúrese de que tiene permisos suficientes en todos los directorios del árbol. De lo contrario, se producirá una excepción y no se devolverá ningún resultado. Al usar EnumerateDirectories en una consulta PLINQ, es problemático controlar las excepciones de E/S de una forma correcta que permita continuar con la iteración. Si el código debe controlar las excepciones de E/S o de acceso no autorizado, debe considerar el enfoque descrito en Cómo: Recorrer en iteración directorios con la clase paralela.

Si la latencia de E/S es un problema, por ejemplo, porque la E/S de archivos se produce a través de una red, considere la posibilidad de usar una de las técnicas de E/S asincrónicas descritas en TPL y la programación asincrónica tradicional de .NET y en esta entrada de blog.

Vea también

Conceptos

Parallel LINQ (PLINQ)

Historial de cambios

Fecha

Historial

Motivo

Mayo de 2010

Nota agregada sobre el uso frente a la aceleración.

Comentarios de los clientes.