方法: PLINQ を使用してファイル ディレクトリを反復処理する

更新 : 2010 年 5 月

この例では、ファイル ディレクトリに対する操作を簡単に並列化する 2 とおりの方法を示します。 最初のクエリでは、GetFiles メソッドを使用して、ディレクトリとすべてのサブディレクトリ内のファイル名の配列を作成します。 配列全体の値が設定されるまでこのメソッドから制御が戻らないため、操作の開始時に待機時間が発生する可能性があります。 ただし、配列が作成されたら、PLINQ は配列を迅速に並列処理できます。

2 番目のクエリでは、即座に結果を返し始める静的な EnumerateDirectories メソッドと EnumerateFiles メソッドを使用します。 この方法は、最初の例に比べると、処理時間が多くの要素に左右される可能性がありますが、大規模なディレクトリ ツリーを反復処理するときには処理が速くなる場合があります。

Caution メモ注意

これらの例は使用法を示すことを目的としており、同等の LINQ to Objects 順次クエリよりも実行速度が遅い場合があります。高速化の詳細については、「PLINQ での高速化について」を参照してください。

使用例

次の例は、単純なシナリオ (ツリー内のすべてのディレクトリにアクセスできる場合、ファイル サイズがそれほど大きくない場合、アクセス時間が重要ではない場合) において、ファイル ディレクトリを反復処理する方法を示しています。 この方法では、ファイル名の配列が作成されている間、最初に待機時間が発生します。


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

次の例は、単純なシナリオ (ツリー内のすべてのディレクトリにアクセスできる場合、ファイル サイズがそれほど大きくない場合、アクセス時間が重要ではない場合) において、ファイル ディレクトリを反復処理する方法を示しています。 この方法では、前の例よりも迅速に結果を生成し始めます。


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

GetFiles を使用する場合は、ツリー内のすべてのディレクトリに対して必要なアクセス許可があることを確認してください。 アクセス許可がないと例外がスローされ、結果は返されません。 PLINQ クエリで EnumerateDirectories を使用する場合、反復処理を続行できるように I/O 例外を適切に処理することが問題となります。 コードで I/O 例外または承認されていないアクセスの例外を処理する必要がある場合は、「方法: Parallel クラスを使用してファイル ディレクトリを反復処理する」で説明する方法を検討することをお勧めします。

ネットワーク経由のファイル I/O などで I/O の待機時間が問題となる場合は、「TPL と従来の .NET 非同期プログラミング」およびこのブログの投稿で説明する非同期 I/O の手法のいずれかを使用することを検討してください。

参照

概念

Parallel LINQ (PLINQ)

履歴の変更

日付

履歴

理由

2010 年 5 月

使用法と高速化に関する説明を 追加。

カスタマー フィードバック