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