Parallel.For の利用 - C#

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

概要

TPL(Task Palallel Library)を利用すると、マルチスレッドのプログラムを実装せずに、シングルスレッドのコード記述でマルチスレッド処理を実装できます。
この記事ではforループをマルチスレッド化する Parallel.For のコード記述について紹介します。

プログラム例

Windows Form アプリケーションを作成します。

UI

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

コード

下記のコードを記述します。フォームに配置した3つのボタンのClickイベントの実装になります。
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 ParallelFor
{
  public partial class FormSimpleParallelFor : Form
  {
    private List<string> Names;

    public FormSimpleParallelFor()
    {
      InitializeComponent();
    }

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

      ParallelLoopResult result = Parallel.For(0, Names.Count, Proc);

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

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

    public void Proc(int index) {
      Names[index] = "「" + Names[index] + "」";
    }

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

      ParallelLoopResult result = Parallel.For(0, Names.Count, index => Proc(index));

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

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

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

      ParallelLoopResult result = Parallel.For(0, Ns.Count, index => Ns[index] = "「" + Ns[index] + "」");

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

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

解説

button1

button1クリック時に実行されるコードは下記になります。
前半部分では、Listオブジェクトの作成とリストに文字列の要素を追加しています。その後の Parallel.For メソッド部分がループの処理になります。第一引数の値から第二引数の値までループ処理がマルチスレッドで実行されます。第三引数に処理を実行するメソッド(関数/プロシージャー)を指定します。第三引数の型は
  • Action<int>
  • Action<long>
  • Action<ParallelLoopState>
  • Action<int, ParallelLoopState>
  • Action<long, ParallelLoopState>
のメソッド型が指定できます、今回の例ではAction<int>を利用しています。
Parallel.For メソッドにより、第三引数のProcメソッドが繰り返し呼び出されます。Procメソッドの第一引数index変数にループカウンタの値が代入されます。マルチスレッド処理のため、Procはループの順番ごとに呼び出されるとは限らないことに注意が必要です。
Procメソッドでは、Names リストの要素の文字列の前後に"「" "」" 文字列を追加し、文字列全体をかぎ括弧で囲む処理をします。

Parallel.Forメソッドの戻り値には ParallelLoopResult オブジェクトが返ります。ParallelLoopResult.IsCompleted プロパティを確認することでループが正常終了したかを確認できます。ループが正常に終了した場合は、Namesリストの値をテキストボックスに表示します。
    private void button1_Click(object sender, EventArgs e)
    {
      Names = new List<string>();
      Names.Add("ぺんぎんクッキー");
      Names.Add("らくだサブレ");
      Names.Add("あひるタルト");
      Names.Add("しろくまアイス");
      Names.Add("ふくろうカステラ");

      ParallelLoopResult result = Parallel.For(0, Names.Count, Proc);

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

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

    public void Proc(int index) {
      Names[index] = "「" + Names[index] + "」";
    }

button2

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

      ParallelLoopResult result = Parallel.For(0, Names.Count, index => Proc(index));

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

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

button3

button3クリック時には下記のコードが実行されます。button1,button2 と同様の処理ですが、button1,button2ではループの処理部分が別のメソッドになっているため、Names変数はクラスのメンバ変数として宣言する必要がありました。ループ処理部分を使いまわさないのであれば、別のメソッドに分けて記述するのは冗長であり、変数もローカル変数で宣言したほうがコードが見やすくなります。
button3では、ラムダ式を利用しループの処理部分もラムダ式内に記述することで、ループの処理部分をParallel.For内に記述しています。このため、リストオブジェクトもローカル変数として宣言しています。
    private void button3_Click(object sender, EventArgs e)
    {
      List<string> Ns = new List<string>();
      Ns.Add("ぺんぎんクッキー");
      Ns.Add("らくだサブレ");
      Ns.Add("あひるタルト");
      Ns.Add("しろくまアイス");
      Ns.Add("ふくろうカステラ");

      ParallelLoopResult result = Parallel.For(0, Ns.Count, index => Ns[index] = "「" + Ns[index] + "」");

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

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

実行結果

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


[Button1]をクリックします。「処理が正常に終了しました」のメッセージの後、リストオブジェクトに設定した文字列が "「」"で囲まれた文字列でテキストボックスに表示されます。


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


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

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

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

      ParallelLoopResult result = Parallel.For(0, Nsd.Count, delegate (int index) { Nsd[index] = "「" + Nsd[index] + "」"; });

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

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