Parallel.ForEach の利用 - C#

C#でTPL (Task Palallel Library) のParallel.ForEach を利用するコードを紹介します。

概要

こちらの記事では TPL (Task Palallel Library) のParallel.For を利用したコードを紹介しました。
Parallel.For ではループカウンタ変数が用意されていましたが、各項目に対して同じ処理を実行する場合はループカウンタ変数が不要な場合があります。ループカウンタ変数が不要な場合は、Parallel.ForEach を利用したほうがシンプルなコード記述ができます。

プログラム例

UI

下図のUIを作成します。複数行テキストボックスとボタンを3つ配置します。

コード

下記のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ParallelForEach
{
  public partial class FormSimpleParallelForEach : Form
  {
    private List<string> Results = new List<string>();


    public FormSimpleParallelForEach()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      List<string> Names = new List<string>();
      Names.Add("ぺんぎんクッキー");
      Names.Add("らくだサブレ");
      Names.Add("あひるタルト");
      Names.Add("しろくまアイス");
      Names.Add("ふくろうカステラ");

      Results.Clear();
      ParallelLoopResult result = Parallel.ForEach(Names, Proc);

      if (result.IsCompleted == true) {
        textBox1.Text += "処理が正常に終了しました。\r\n\r\n";

        foreach (string s in Results) {
          textBox1.Text += s + "\r\n";
        }
      }
    }

    public void Proc(string value, ParallelLoopState state)
    {
      Results.Add("“" + value + "”");
    }

    private void button2_Click(object sender, EventArgs e)
    {
      List<string> Names = new List<string>();
      Names.Add("ぺんぎんクッキー");
      Names.Add("らくだサブレ");
      Names.Add("あひるタルト");
      Names.Add("しろくまアイス");
      Names.Add("ふくろうカステラ");

      Results.Clear();
      ParallelLoopResult result = Parallel.ForEach(Names, (v, s) => Proc(v,s));

      if (result.IsCompleted == true) {
        textBox1.Text += "処理が正常に終了しました。\r\n\r\n";

        foreach (string s in Results) {
          textBox1.Text += s + "\r\n";
        }
      }
    }

    private void button3_Click(object sender, EventArgs e)
    {
      List<string> Names = new List<string>();
      Names.Add("ぺんぎんクッキー");
      Names.Add("らくだサブレ");
      Names.Add("あひるタルト");
      Names.Add("しろくまアイス");
      Names.Add("ふくろうカステラ");

      List<string> RList = new List<string>();
      ParallelLoopResult result = Parallel.ForEach(Names, (v, s) => RList.Add("“" + v + "”"));

      if (result.IsCompleted == true) {
        textBox1.Text += "処理が正常に終了しました。\r\n\r\n";

        foreach (string s in RList) {
          textBox1.Text += s + "\r\n";
        }
      }
    }
  }
}

解説

button1

button1クリック時に実行されるコードは下記になります。
前半部分では、Listオブジェクトの作成とリストに文字列の要素を追加しています。その後の Parallel.ForEach メソッド部分がループの処理になります。第一引数のNames リストの各要素に対して第二引数の Proc メソッドの処理が実行されます。
第二引数には以下の型を指定できます。
  • Action<TSource>
  • Action<TSource, Parallel​Loop​State>
  • Action<TSource, Parallel​Loop​State, Int64>
今回の例では、Action<TSource> を利用しています。(TSource = string)

ProcメソッドにはNamesリストオブジェクトの個々の要素の値であるstring型が第一引数で渡されます。Procメソッドではリストの文字列の前に「“」を文字列の後に「”」を付加しています。処理された結果の文字列はResultsオブジェクトのAddメソッドで結果セットに追加します。
処理の結果はParallelLoopResult オブジェクトで受け取ります。IsCompleted プロパティの値を判定し true であれば処理が正常に終了したメッセージを表示し、テキストボックスに処理の結果文字列を表示します。Parallel.Forの場合はループカウンタ変数が渡されるため、Namesリストの要素を直接参照して処理結果の値に置き換えることができましたが、Parallel。ForEachの場合は値のみが渡されるため結果を格納するリストを別途用意する必要があります。
private void button1_Click(object sender, EventArgs e)
{
  List<string> Names = new List<string>();
  Names.Add("ぺんぎんクッキー");
  Names.Add("らくだサブレ");
  Names.Add("あひるタルト");
  Names.Add("しろくまアイス");
  Names.Add("ふくろうカステラ");

  Results.Clear();
  ParallelLoopResult result = Parallel.ForEach(Names, Proc);

  if (result.IsCompleted == true) {
    textBox1.Text += "処理が正常に終了しました。\r\n\r\n";

    foreach (string s in Results) {
      textBox1.Text += s + "\r\n";
    }
  }
}

public void Proc(string value, ParallelLoopState state)
{
  Results.Add("“" + value + "”");
}

button2

button2をクリックした場合は下記のコードが実行されます。button1と全く同じ処理ですが、Parallel.ForEach メソッドの第二引数にメソッド名ではなくラムダ式を用いた記述にしています。
private void button2_Click(object sender, EventArgs e)
{
  List<string> Names = new List<string>();
  Names.Add("ぺんぎんクッキー");
  Names.Add("らくだサブレ");
  Names.Add("あひるタルト");
  Names.Add("しろくまアイス");
  Names.Add("ふくろうカステラ");

  Results.Clear();
  ParallelLoopResult result = Parallel.ForEach(Names, (v, s) => Proc(v,s));

  if (result.IsCompleted == true) {
    textBox1.Text += "処理が正常に終了しました。\r\n\r\n";

    foreach (string s in Results) {
      textBox1.Text += s + "\r\n";
    }
  }
}

button3

button3をクリックした場合は下記のコードが実行されます。button1, button2 と同じ処理ですが、Parallel.ForEach メソッドの第二引数にラムダ式を与えています。button2 ではラムダ式内からProc メソッドを呼び出す記述でしたが、button3では文字列の処理もラムダ式内に実装しています。
button1, button2 ではResults オブジェクトをクラスのメンバ変数として宣言する必要がありましたが、button3では、この記述にすることで、結果を格納するリストRList オブジェクトをローカル変数として宣言できます。
private void button3_Click(object sender, EventArgs e)
{
  List<string> Names = new List<string>();
  Names.Add("ぺんぎんクッキー");
  Names.Add("らくだサブレ");
  Names.Add("あひるタルト");
  Names.Add("しろくまアイス");
  Names.Add("ふくろうカステラ");

  List<string> RList = new List<string>();
  ParallelLoopResult result = Parallel.ForEach(Names, (v, s) => RList.Add("“" + v + "”"));

  if (result.IsCompleted == true) {
    textBox1.Text += "処理が正常に終了しました。\r\n\r\n";

    foreach (string s in RList) {
      textBox1.Text += s + "\r\n";
    }
  }
}

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


[button1]をクリックします。「処理が正常に終了しました。」の文字列が表示され、元の文字列が「“」「”」で囲まれた文字列がテキストボックスに表示されます。


[button2]をクリックします。button1をクリックした結果と同じ内容がテキストボックスに表示されます。


[button3]をクリックします。button1,button2をクリックした結果と同じ内容がテキストボックスに表示されます。

参考:匿名関数を利用する記述

ラムダ式の代わりに、下記のコードのように匿名メソッドを利用した記述でも実行可能です。
private void button4_Click(object sender, EventArgs e)
{
  List<string> Names = new List<string>();
  Names.Add("ぺんぎんクッキー");
  Names.Add("らくだサブレ");
  Names.Add("あひるタルト");
  Names.Add("しろくまアイス");
  Names.Add("ふくろうカステラ");

  List<string> RList = new List<string>();
  ParallelLoopResult result = Parallel.ForEach(Names, delegate(string value, ParallelLoopState state) { RList.Add("“" + value + "”"); });

  if (result.IsCompleted == true) {
    textBox1.Text += "処理が正常に終了しました。\r\n\r\n";

    foreach (string s in RList) {
      textBox1.Text += s + "\r\n";
    }
  }
}
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2018-10-15
iPentec all rights reserverd.