|
|
連載:C# 4入門
第3回 TaskクラスとPLINQ(Parallel LINQ)
株式会社ピーデー 川俣 晶
2010/09/17 |
|
|
待てといわれて待つヤツがいるぞ
Taskクラスでは複数の処理を実行することができる。その場合、知りたいタイミングは恐らく2つだ。1つは、1つでもタスクが終了したとき。もう1つは、すべてのタスクが終了したときだろう。これも簡単に判定できる。
Task.WaitAnyメソッドとTask.WaitAllメソッドは引数にTaskオブジェクトを列挙でき、それぞれ1つでも終わったとき、すべて終わったときに戻ってくる機能を持つ。これを使えば、複数のタスクを作って終了を待つ場合でも、簡単にコードを書ける。
using System;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
var task1 = Task.Factory.StartNew(() =>
{
for (int i = 0; i < 100; i++) Console.Write('A');
});
var task2 = Task.Factory.StartNew(() =>
{
for (int i = 0; i < 200; i++) Console.Write('B');
});
var task3 = Task.Factory.StartNew(() =>
{
for (int i = 0; i < 300; i++) Console.Write('C');
});
Task.WaitAny(task1, task2, task3);
Console.WriteLine();
Console.WriteLine("stopped one task");
Task.WaitAll(task1, task2, task3);
Console.WriteLine();
Console.WriteLine("stopped all tasks");
}
}
|
|
リスト3 |
task3.cs |
ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
stopped one task
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBB
stopped all task
|
|
リスト3の実行結果 |
環境やタイミングに依存する。 |
さて、問題はその先である。Task.WaitAnyメソッドとTask.WaitAllメソッドは便利だが、それでもスレッド関係に比べると機能が少ない。また、いくら簡単になったといっても、同期の問題は丸ごと残る。別のタスクから同じリソースを使うときは、やはりlockステートメントなどを使わねばならない。果たして、それにもかかわらずタスクを使うべきなのだろうか?
答えは「使うべきである」だ。なぜなら、「気軽に使えるハードルの低さ」こそがTaskクラスの身上であり、それを使うことがメニー・コア時代に対応する1つの処方箋だからである。いくら同期の問題が……といっても、それが問題にならないケースや、問題になっても軽いこともある。常にヘビーなソース・コードを想定する必要はない。それよりも、並列実行できる処理を別タスクに分散させる習慣を付ける方が、よほど重要だろう。それが複数コアを持つ未来型CPUのパワーを引き出す早道だからである。タスクの終了は待ってくれるが、時代は待ってくれないのである。
PLINQ(Parallel Linq)
一見、並列処理の目玉に見えるPLINQだが、後回しにしたのは結局扱いがおどろくほど簡単だからにすぎない。
まず、クエリ式を含む簡単なソースを作成した。プログラムそのものには意味がない。「2」を探して、それを出力するだけである。
using System;
using System.Linq;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
int[] ar = { 1, 2, 3 };
var q1 = from n in ar
where n == 2
select n;
foreach (var n in q1)
Console.WriteLine("found {0}", n);
}
}
|
|
リスト4 |
これをパラレル(並列)対応するには、クエリ式にAsParallelメソッドを追加するだけである。
var q1 = from n in ar.AsParallel()
where n == 2
select n;
|
|
メソッド形式でも同じである。例えば、こういうクエリがあったとしよう。
var q1 = ar.Where((c) => c == 2);
|
|
これは以下のように、メソッド・チェーンの最初にAsParallelメソッドを挟むだけである。
var q1 = ar.AsParallel().Where((c) => c == 2);
|
|
がく然とするほど簡単である。これといって追加のトピックもない。メソッド1つでクエリがパラレル化されるのである。しかし、これで有り難みが大きいかといえば、必ずしもそうではない。というのは、以下のような状況が存在するからである。
- 別のサーバにクエリを投げる場合など、クエリを受け取って実行する側の性能に依存する要素が大きい(ローカル側のパラレルの問題ではない。PLINQは基本的にローカル側で処理されるLinq to Objects用の機能。Linq to SQLなどで有効というわけではない)
- 湯水のごとく大量に平然とクエリ式を消費するようになると、ほとんどのケースでクエリ式はボトルネックではなく、いちいちAsParallelメソッドを挿入してもあまりメリットはなく、むしろ手間がかかり、ソース・コードの可読性も落ちてしまう
- AsParallelメソッドを入れると、結果が変わってしまう場合がある
問題は最後の1つである。いったい何が変わってしまうのだろうか。
Insider.NET 記事ランキング
本日
月間