一定のパターンで計算を繰り返す処理が大量にあったので、C#で並列処理を試してみました。
条件としては、
- 計算の処理は独立していて、他の処理と依存性がない。
- パフォーマンスを考えて並列処理は同時2~4個に制限
はじめに、Paralell.ForEach()を使って、以下のようにしました。
private void DoCalc_Click(object sender, RoutedEventArgs e)
{
System.Random rnd = new System.Random();
List<int> myCalcs = new List<int>();
for (var i = 0; i < 10; i++)
{
myCalcs.Add(rnd.Next(10000));// 10,000 未満の整数
}
// 並列実行オプション
ParallelOptions options = new ParallelOptions();
options.MaxDegreeOfParallelism = 4; // 最大実行数
Parallel.ForEach(myCalcs, options, calc =>
{
// 計算実行
DoSomething(calc);
});
}
/// <summary>
/// なんらかの処理
/// </summary>
/// <param name="t"></param>
private void DoSomething(int t)
{
Thread.Sleep(t);
}
ところが、これWPFアプリケーションなんですよね。並列処理がはじまると、WPFアプリケーションがフリーズします。UI側のスレッドに処理が戻らないので、当たり前です。
Task.Run()
ここで、処理をawait Task.Run()で別のスレッドにしてみました。
Parallel.ForEach(myCalcs, options, async calc =>
{
// 計算実行
await Task.Run(() => DoSomething(calc));
});
これでうまく行きそうな気がしたんですが、リストに登録されている計算が際限なく並列処理に投入されます。よく考えれば当たり前ですが、Task.Run()すると、並列処理とは別スレッドになります。Paralell.ForEach()はスレッドが空いたので、次の処理を投入します。これじゃ同時実行数を設定している意味がありません。
Task.Run() → Paralell.ForEach()
しばらく考えて、Pralell.ForEach()そのものをawaite Task.Run()で処理するようにしてみました。
private async void DoCalc_Click(object sender, RoutedEventArgs e)
{
await Task.Run(()=>DoParalell());
}
/// <summary>
/// 並列処理
/// </summary>
private void DoParalell()
{
System.Random rnd = new System.Random();
List<int> myCalcs = new List<int>();
for (var i = 0; i < 10; i++)
{
myCalcs.Add(rnd.Next(10000));// 10,000 未満の整数
}
// 並列実行オプション
ParallelOptions options = new ParallelOptions();
options.MaxDegreeOfParallelism = 4; // 最大実行数
Parallel.ForEach(myCalcs, options, calc =>
{
// 計算実行
DoSomething(calc);
});
}
これで呼び出し側はParalell.ForEach()の処理を別スレッドで処理します。すぐにUIに処理が戻るのでWPFアプリケーションはフリーズしなくなります。
並列処理は同時実行数の範囲内で上手く処理されるようになりました。
動作環境
以下の環境で動作を確認しています。
- .NET Framework 4.5.2
- Visual Studio Professional 2017
- Windows 10 Pro(1809)