Udostępnij za pośrednictwem


Porady: Iteracja po katalogach plików z wykorzystaniem technologii PLINQ

W tym artykule przedstawiono dwa sposoby równoległości operacji w katalogach plików. Pierwsze zapytanie używa GetFiles metody , aby wypełnić tablicę nazw plików w katalogu i wszystkich podkatalogach. Ta metoda może wprowadzić opóźnienie na początku operacji, ponieważ nie zwraca się do momentu wypełnienia całej tablicy. Jednak po wypełnieniu tablicy funkcja PLINQ może przetwarzać ją równolegle.

Drugie zapytanie używa metod statycznych EnumerateDirectories i EnumerateFiles , które natychmiast zaczynają zwracać wyniki. Takie podejście może być szybsze w przypadku iteracji w dużych drzewach katalogów, ale czas przetwarzania w porównaniu z pierwszym przykładem zależy od wielu czynników.

Uwaga

Te przykłady mają na celu zademonstrowanie użycia i może nie działać szybciej niż równoważne sekwencyjne zapytanie LINQ to Objects. Aby uzyskać więcej informacji na temat przyspieszania, zobacz Understanding Speedup in PLINQ (Opis szybkości w PLINQ).

Przykład getFiles

W tym przykładzie pokazano, jak iterować katalogi plików w prostych scenariuszach, gdy masz dostęp do wszystkich katalogów w drzewie, rozmiary plików nie są duże, a czasy dostępu nie są znaczące. Takie podejście obejmuje okres opóźnienia na początku podczas konstruowania tablicy nazw plików.


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

Przykład wyliczeniaFiles

W tym przykładzie pokazano, jak iterować katalogi plików w prostych scenariuszach, gdy masz dostęp do wszystkich katalogów w drzewie, rozmiary plików nie są duże, a czasy dostępu nie są znaczące. Takie podejście rozpoczyna generowanie wyników szybciej niż w poprzednim przykładzie.

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

W przypadku korzystania z programu GetFilesupewnij się, że masz wystarczające uprawnienia do wszystkich katalogów w drzewie. W przeciwnym razie zostanie zgłoszony wyjątek i nie zostaną zwrócone żadne wyniki. W przypadku używania EnumerateDirectories elementu w zapytaniu PLINQ problematyczne jest obsługę wyjątków we/wy w sposób zgrabny, który umożliwia kontynuowanie iteracji. Jeśli kod musi obsługiwać wyjątki we/wy lub nieautoryzowanego dostępu, należy rozważyć podejście opisane w temacie Instrukcje: iterowanie katalogów plików z klasą równoległą.

Jeśli opóźnienie we/wy jest problemem, na przykład w przypadku operacji we/wy pliku za pośrednictwem sieci, rozważ użycie jednej z technik asynchronicznych we/wy opisanych w temacie TPL i Tradycyjne programowanie asynchroniczne platformy .NET i w tym wpisie w blogu.

Zobacz też