BlockingCollection クラス

Last Updated 2011/09/21


BlockingCollection クラスに関する情報を仕入れようと WEB サイトをウロウロしてみましたが、通常のコレクションと同じ使い方をしているものばかりで参考になるものがありません。通常のコレクションと同じ使い方ができるということは分かりますが、テストしたところ、シーケンシャル処理するほうが高速になるものもあるほどです。

さて、BlockingCollection クラスは内部的には ConcurrentQueue<T> クラスを使っていますから基本的には ConcurrentQueue<T> クラスと同じと考えていいと思います。したがって、ConcurrentQueue<T> クラスとは何かが分かれば自動的に BlockingCollection クラスの本質が分かると考えます。

Note ConcurrentQueue<T> クラス以外のクラスをベースにしたい場合は、コンストラクタの IProducerConsumerCollection<T> インターフェースを継承するクラスを指定する構文を使ってインスタンスを作成 すれば可能です。

ConcurrentQueue<T> クラス、複数のスレッドから同時にアクセス可能な先入れ先出しコレクションクラスです。このクラスに対する .NET Framework SDK の解説では、「スレッドセーフな先入れ先出しコレクション」とありますが、通常スレッドセーフというと、別のスレッドからの影響を受けないという意味で使います。しかし、このクラスにおけるそれは逆です。つまり、複数のスレッドから同時にアクセスしても自動的に交通整理をしてアクセス可能にするからです。

private void button1_Click(object sender, EventArgs e)
{
  var queue = new ConcurrentQueue<string>();

  Parallel.For(0, 10, (n) =>
    {
      queue.Enqueue(String.Format("Index= {0},  Task.CurrentId= {1},  Thread.ManagedThreadId= {2}",
          n, Task.CurrentId, Thread.CurrentThread.ManagedThreadId));
    });

  string s;
  textBox1.Text = "";

  while (queue.TryDequeue(out s))
  {
    textBox1.Text += s + "\r\n";
  }
}

実行結果:

Index= 0,  Task.CurrentId= 15,  Thread.ManagedThreadId= 10
Index= 1,  Task.CurrentId= 15,  Thread.ManagedThreadId= 10
Index= 3,  Task.CurrentId= 15,  Thread.ManagedThreadId= 10
Index= 4,  Task.CurrentId= 15,  Thread.ManagedThreadId= 10
Index= 2,  Task.CurrentId= 16,  Thread.ManagedThreadId= 13
Index= 5,  Task.CurrentId= 15,  Thread.ManagedThreadId= 10
Index= 8,  Task.CurrentId= 16,  Thread.ManagedThreadId= 13
Index= 9,  Task.CurrentId= 16,  Thread.ManagedThreadId= 13
Index= 6,  Task.CurrentId= 15,  Thread.ManagedThreadId= 10
Index= 7,  Task.CurrentId= 15,  Thread.ManagedThreadId= 10

一部のスレッド ID が異なっていますが、これは異なるスレッドから同じ ConcurrentQueue<T> オブジェクトにアクセスしていることを意味します。つまり、"concurrent" の「同時」は、異なるスレッドから一つのコレクションに同時にアクセスするのそれです。.NET Framework が自動的に交通整理をして、混乱することを避けてくれます。

BlockingCollection クラスの根底にあるものは分かりました。次は "Block" の意味です。

ブロック "Block" は、「抑止する」とか「妨害する」という意味ですが、コレクションが空になると項目の削除をブロックし、指定の最大容量を超えて項目を追加しようとしても項目を削除して追加可能になるまでブロックします。BlockingCollection クラスは、用語の解説のページで説明した「プロデューサー/コンシューマー パターン」を実現するクラスです。

さて、ここらで BlockingCollection クラスの使用例を示すべきですが、具体的で実用的なものを思いつきません。これは実用的だなと思うものを WEB サイトで見つけましたが、シーケンシャル処理との比較をしてみるとシーケンシャル処理のほうが速いという結果になり、ガッカリしました。要するに、並列処理をすれば何でも速くなるわけではないとあらためて思い知らされました。

私も実用的な使用例を示すことはできませんが、まずシーケンシャル処理でコードを作成し、その後、並列処理版を作って処理速度を比較してから並列処理を採用すべきかどうかを決めることをすすめます。シーケンシャル処理のほうが安全で確実であることに疑問の余地はありませんから。

−以上−