共用方式為


HOW TO:使用平行類別逐一查看檔案目錄

在許多情況下,檔案反覆運算是可輕易平行處理的作業。 HOW TO:使用 PLINQ 逐一查看檔案目錄主題說明在許多案例下執行此工作最簡單的方式。 不過,當您的程式碼必須處理存取檔案系統時可能發生的許多例外狀況類型時,可能會增加複雜度。 下列範例示範處理問題的一個作法。 它使用以堆疊為基礎的反覆項目,周遊指定之目錄下的所有檔案和資料夾,可讓您的程式碼攔截及處理各種例外狀況。 當然,處理例外狀況的方式是由您決定。

範例

在下列範例中,目錄的反覆運算是循序執行,但是檔案處理則是平行執行。 當檔案與目錄的比例很高時,這可能是最佳作法。 另外,也可以平行處理目錄反覆運算,而循序存取每個檔案。 除非是特別以具有大量處理器的電腦為目標,否則平行處理兩個迴圈可能沒有效率。 但無論如何,您都應該徹底測試應用程式,判斷最佳作法。

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 非同步程式設計

請注意,如果在主執行緒上擲回例外狀況,由 ForEach 方法所啟動的執行緒可能會繼續執行。 若要停止這些執行緒,您可以在例外處理常式中設定布林值變數,在每次平行迴圈反覆運算時檢查其值。 如果值表示已擲回例外狀況,請使用 ParallelLoopState 變數停止或中斷迴圈。 如需詳細資訊,請參閱 HOW TO:停止或中斷 Parallel.For 迴圈

請參閱

概念

資料平行處理原則 (工作平行程式庫)