방법: PLINQ를 사용하여 파일 디렉터리 열거

이 문서에서는 파일 디렉터리에서 작업을 병렬 처리하는 두 가지 방법을 보여 줍니다. 첫 번째 쿼리는 GetFiles 메서드를 사용하여 디렉터리 및 모든 하위 디렉터리에서 파일 이름 배열을 채웁니다. 이 메서드는 전체 배열이 채워질 때까지 반환되지 않으므로 작업 시작 시 대기 시간이 발생할 수 있습니다. 그러나 배열이 채워진 후 PLINQ는 메서드를 빠르게 병렬 처리할 수 있습니다.

두 번째 쿼리는 결과를 즉시 반환하기 시작하는 정적 EnumerateDirectoriesEnumerateFiles 메서드를 사용합니다. 큰 디렉터리 트리를 반복 중일 경우 이 방법이 더 빠를 수 있지만, 처리 시간은 첫 번째 예제와 비교하여 많은 요소에 따라 다릅니다.

참고 항목

이 예제는 사용법을 보여 주기 위해 제공되며 동일한 순차적 LINQ to Objects 쿼리보다 빠르게 실행되지 않을 수 있습니다. 속도 향상에 대한 자세한 내용은 PLINQ의 속도 향상 이해를 참조하세요.

GetFiles 예제

다음 예제에서는 트리의 모든 디렉터리에 액세스할 수 있고, 파일 크기가 크지 않으며, 액세스 시간이 길지 않을 경우 파일 디렉터리를 반복하는 방법을 간단한 시나리오로 보여 줍니다. 이 방법에는 파일 이름 배열이 생성되는 동안 시작 시에 대기 시간이 포함됩니다.


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

EnumerateFiles 예제

다음 예제에서는 트리의 모든 디렉터리에 액세스할 수 있고, 파일 크기가 크지 않으며, 액세스 시간이 길지 않을 경우 파일 디렉터리를 반복하는 방법을 간단한 시나리오로 보여 줍니다. 이 방법은 이전 예제보다 빠르게 결과를 생성하기 시작합니다.

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

GetFiles를 사용할 경우 트리의 모든 디렉터리에 충분한 권한이 있는지 확인하세요. 그렇지 않으면 예외가 throw되고 결과가 반환되지 않습니다. PLINQ 쿼리에서 EnumerateDirectories를 사용할 경우 반복을 계속할 수 있는 정상적인 방법으로 I/O 예외를 처리하는 것이 어렵습니다. 코드에서 I/O 또는 인증되지 않은 액세스 예외를 처리해야 하는 경우에는 방법: 병렬 클래스를 사용하여 파일 디렉터리 반복에 설명된 방법을 고려해야 합니다.

예를 들어 I/O 지연이 네트워크를 통한 파일 I/O와 관련된 문제인 경우 TPL 및 일반적인 .NET 비동기 프로그래밍 및 이 블로그 게시물에 설명된 비동기 I/O 기법 중 하나를 사용하는 것이 좋습니다.

참고 항목