다음을 통해 공유


방법: 병렬 클래스를 사용하여 파일 디렉터리 열거

대부분의 경우 파일 반복은 쉽게 병렬 처리할 수 있는 작업입니다. 방법: PLINQ를 사용하여 파일 디렉터리 열거 항목에서는 대부분의 일반 시나리오에서 이 작업을 가장 쉽게 수행할 수 있는 방법을 보여 줍니다. 그러나 코드에서 파일 시스템에 액세스할 때 발생할 수 있는 여러 종류의 예외를 처리해야 하는 경우 문제가 발생할 수 있습니다. 다음 예제에서는 문제를 해결할 수 있는 한 가지 방법을 보여 줍니다. 이 예제에서는 스택 기반 반복을 사용하여 지정된 디렉터리 아래의 모든 폴더와 파일을 트래버스하고 코드에서 다양한 예외를 catch하여 처리할 수 있도록 설정합니다. 물론 예외를 처리하는 방법은 사용자가 선택할 수 있습니다.

예제

다음 예제에서 디렉터리에 대한 반복은 순차적으로 수행되고 파일 처리는 병렬로 수행됩니다. 파일과 디렉터리 간 비율이 큰 경우 이 방법이 가장 좋은 방법일 수 있습니다. 이 방법에서는 디렉터리 반복을 병렬 처리하고 각 파일에 순차적으로 액세스할 수도 있습니다. 특별히 많은 수의 프로세서가 포함되어 있는 컴퓨터를 대상으로 하지 않는 경우 두 루프를 모두 병렬 처리하는 것은 효율적이지 않을 수 있습니다. 그러나 모든 경우 응용 프로그램을 완벽하게 테스트하여 가장 좋은 방법을 결정해야 합니다.

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

이 예제에서는 파일 I/O가 동기적으로 수행됩니다. 큰 파일 또는 느린 네트워크 연결을 처리할 때는 파일에 비동기적으로 액세스하는 것이 좋을 수 있습니다. 병렬 반복에 비동기 I/O 방법을 결합할 수 있습니다. 자세한 내용은 TPL 및 일반적인 .NET 비동기 프로그래밍을 참조하십시오.

주 스레드에서 예외가 throw되는 경우 ForEach 메서드에 의해 시작된 스레드가 계속해서 실행될 수 있습니다. 예외 처리기에서 부울 변수를 설정하고 병렬 루프의 각 반복에서 해당 값을 확인하여 이러한 스레드를 중지할 수 있습니다. 값이 예외가 throw되었음을 나타내는 경우 ParallelLoopState 변수를 사용하여 루프를 중지하거나 루프에서 빠져 나옵니다. 자세한 내용은 방법: Parallel.For 루프에서 중지 또는 중단을 참조하십시오.

참고 항목

개념

데이터 병렬 처리(작업 병렬 라이브러리)