[C#] 40. Linqを利用した並列処理(Parallel)を使い方


Study / C#    作成日付 : 2020/05/13 17:37:13   修正日付 : 2021/09/29 19:29:03

こんにちは。明月です。


この投稿はC#でLinqを利用した並列処理(Parallel)を使い方に関する説明です。


以前の投稿で私がThreadに関して説明したことがあります。

link - [C#] 37. スレッド(Thread)を使い方、Thread.Sleep関数を使い方


スレッドというのはプログラム内でいろんな処理を同時に処理、つまり、同時に処理してシステムの性能を向上させる機能です。

でも、このスレッドは個数が無限的に増やすこともできないし、どのぐらいにスレッドが多くなると性能低下になるため、個数管理するThreadPoolが存在します。しかし、このThreadPoolはJoin機能が無くて、EventWaitHandleクラスで同期化する機能を別に作成しなければならない不便なこともあります。


Linq式ではこの問題を解決する並列処理を作成できます。

using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading;

namespace Example
{
  // 例クラス
  class Node
  {
    // コンソールに出力콘する時に使うテキスト
    public string Text { get; set; }
    // 繰り返し回数
    public int Count { get; set; }
    // Sleepの時間チック
    public int Tick { get; set; }
  }
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // Nodeクラスのリスト
      var list = new List<Node>();
      // リストにインスタンスを追加
      list.Add(new Node { Text = "A", Count = 3, Tick = 1000 });
      list.Add(new Node { Text = "B", Count = 5, Tick = 10 });
      list.Add(new Node { Text = "C", Count = 2, Tick = 500 });
      list.Add(new Node { Text = "D", Count = 7, Tick = 300 });
      list.Add(new Node { Text = "E", Count = 4, Tick = 200 });
      // リストにParallel設定
      list.AsParallel()
         // 並列処理個数設定(最大2개)
        .WithDegreeOfParallelism(2)
        // 並列処理実行
        .ForAll(x =>
        {
          // 設定された繰り返しの回数程
          for (int i = 0; i < x.Count; i++)
          {
            // コンソール出力
            Console.WriteLine(x.Text + " = " + i);
            // 設定されたSleep時間チック
            Thread.Sleep(x.Tick);
          }
          // 完了、コンソールに出力
          Console.WriteLine("Complete " + x.Text);
        });
      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


上の例をみればNodeクラスのインスタンスをListに格納して、Linq式のAsParallel関数を呼び出すことなら並列処理になります。

WithDegreeOfParallelismの関数を通ってParallelのスレッドの最大個数を設定してForAllを通って実行します。


ForAllはラムダ式で設定してパラメータはListに設定されたNodeインスタンスを取得して実行します。

ここまで見るとThreadとThreadPoolの欠点がなくなる機能ではないかと思います。


Parallel機能をListだけ付けて使うことではなく、staticタイプのクラスで独立的に使うことができます。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Example
{
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // Parallel staticクラス
      // 0から4まで
      Parallel.For(0, 5, x =>
      {
        // 0から2まで繰り返し
        for (int i = 0; i < 3; i++)
        {
          // コンソール出力
          Console.WriteLine(x + " = " + i);
          // スレッド待機
          Thread.Sleep(1);
        }
      });

      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


Parallelクラスの並列処理は基本の形はFor関数です。

意味はforの初期式、条件式、増減式をParallelで設定することです。つまり、forを並列に処理することです。

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace Example
{
  // 例クラス
  class Node
  {
    // コンソールに出力콘する時に使うテキスト
    public string Text { get; set; }
    // 繰り返し回数
    public int Count { get; set; }
    // Sleepの時間チック
    public int Tick { get; set; }
  }
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // Nodeクラスのリスト
      var list = new List<Node>();
      // リストにインスタンスを追加
      list.Add(new Node { Text = "A", Count = 3, Tick = 1000 });
      list.Add(new Node { Text = "B", Count = 5, Tick = 10 });
      list.Add(new Node { Text = "C", Count = 2, Tick = 500 });
      list.Add(new Node { Text = "D", Count = 7, Tick = 300 });
      list.Add(new Node { Text = "E", Count = 4, Tick = 200 });
      // Parallel staticクラス
      // listの個数程
      Parallel.ForEach(list, x =>
      {
        // 設定された繰り返しの回数程
        for (int i = 0; i < x.Count; i++)
        {
          // コンソール出力
          Console.WriteLine(x.Text + " = " + i);
          // 設定されたSleep時間チック
          Thread.Sleep(x.Tick);
        }
        // 完了、コンソールに出力
        Console.WriteLine("Complete " + x.Text);
      });
      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


ParallelのForEachはforeach文を並列に処理する関数です。

形式はListタイプのデータを始めのパラメータに入れて、ラムダ式で各データを取得して並列処理になります。

using System;
using System.Threading.Tasks;

namespace Example
{
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // Parallel staticクラス
      // 4個のラムダ式を並列処理
      Parallel.Invoke(() =>
      {
        Console.WriteLine("A");
      }, () =>
      {
        Console.WriteLine("B");
      }, () =>
      {
        Console.WriteLine("C");
      }, () =>
      {
        Console.WriteLine("D");
      });
      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


上のれ例は別に関係ないラムダ式を並列処理にしました。Invoke関数のパラメータはparamsの処理なので可変的にラムダ式を入れられます。


この投稿を作成しながら思いましたが、Linq式のAsParallel()の関数は自分もよく使える関数です。Parallelクラスは別に使ったらいい利点がなさそうです。

実は私も投稿を書く前には単純にAsParallelをstatic関数スタイルで表現する方法だと思ったのに、それではないですね。

性能上の利点もないし、スレッドを管理する関数やプロパティもないし、単純にfor文を並列に処理することですが、これを使うことよりThreadPoolを使う方がもっといいではないかと思います。


ここまでC#でLinqを利用した並列処理(Parallel)を使い方に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

最新投稿