連載:C# 4入門

第3回 TaskクラスとPLINQ(Parallel LINQ)

株式会社ピーデー 川俣 晶
2010/09/17
Page1 Page2 Page3

天は自らタスクるものをタスク

 「語る者」と「書く者」の間には温度差がある。コーディングに関していえば、実際にコードを書いて感じる重要度と、声高に語る者たちがアピールする点はしばしば異なっている。

 ここはとても大切だから強調しておこう。語られる重要機能と、実際に書いて(使って)有り難みを感じる機能はしばしば食い違う。だから、「語りやすい機能」「語ってアピールしやすい機能」「語って説得力を持たせやすい機能」は、意外と「語る者」の支持を得やすく、ネットに情報が氾濫しやすい。

 しかし、そうやって情報が氾濫した機能が常に「書く者」にとっても素晴らしいかといえば、必ずしもそうではない。書く者にとって重要なのは、意外と泥臭い機能であったりする。書く者が欲しい機能とはズレがある素晴らしい機能(結局は使い物にならない)が用意されることよりも、欲しい機能を自分で書くための基本機能が整備されていくことに価値を見出すかもしれない。

 .NET Framework 4における並列処理のための2つの仕組みである、TaskクラスとPLINQ(Parallel Linq)も、実はそういう関係にあるような気がする。

 Taskクラスを使えば、いとも簡単に、息をするぐらいの当たり前の手間で、処理を並列に実行させることができる。しかし、世間の注目はどうしてもクエリの並列化であるPLINQに集まる。LINQのクエリが並列になることは偉大であると思われてしまうが、PLINQはクエリしか並列に処理できない。今回の最後で説明するとおり、これにはメソッドを1つ追加するだけでよい。しかし、パフォーマンス上の問題に遭遇するまでは、あまり使う必要はないだろう。

 それに対して、Taskクラスは用途を限定せず任意のコードを並列に実行させる汎用的な機能であり、並列に実行されるべきコードを簡単に実現してくれる。こちらの方が出番は多いかもしれない。どちらか一方を覚えておくとすれば、やはりTaskクラスだろう。

Taskクラスの基本的な使い方

 スレッドを改良して、より軽量で使いやすくしたのがTaskクラスということになる。だから、機能は似ているが別物である。使い方も同じではない。

 Taskクラスによるタスクを開始させるにはTask.Factory.StartNewメソッドを呼び出すだけでよい。

using System;
using System.Threading.Tasks;

class Program
{
  static void Main(string[] args)
  {
    // サブ・タスク
    var task = Task.Factory.StartNew(() =>
      {
        for (int i = 0; i < 100; i++) Console.Write('B');
      });

    // メイン・タスク
    for (int i = 0; i < 100; i++) Console.Write('A');

    task.Wait();
  }
}
リスト1
task1.cs

AAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
リスト1の実行結果
環境やタイミングで異なる。

 メイン・タスクはAを、サブ・タスクはBを出力しているが、並列に実行されているので、両者が入り交じっている。どういう順番になるか、結果は不定である。

 最後のWaitメソッドは、Taskクラスによる処理の終了を待つ機能を持つ。これで、確実にサブ・タスクが終わってからプログラムを終了できる。

準備が終わったら次はこれやっといて

 ある条件が成立したときに実行すべきコードをあらかじめ指定しておくというのは、よくある1つのパターンだ。

 特に多いのが、「ある仕事が終わったら、これを実行させたい」だろう。Taskクラスを使用すると、これも簡単だ。

using System;
using System.Threading.Tasks;

class Program
{
  private static void notify(Task t)
  {
    Console.WriteLine();
    Console.WriteLine("sub task {0} done", t.Id);
  }

  static void Main(string[] args)
  {
    var task = Task.Factory.StartNew(() =>
      {
        for (int i = 0; i < 100; i++) Console.Write('B');
      });
    task.ContinueWith(notify);

    for (int i = 0; i < 100; i++) Console.Write('A');

    task.Wait();
  }
}
リスト2

BBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
sub task 1 done
リスト2の実行結果

 このとおり、ContinueWithメソッドで指定するだけである。もちろん実行内容をラムダ式で書いてもよい。これで、指定されたタスクの実行後に、このメソッドが実行される。また、ContinueWithメソッドは同じTaskオブジェクトに対して複数回使ってよく、もちろん順番に実行されることになる。

 ちなみに、notifyメソッドをラムダ式に書き直すと、ContinueWithメソッド呼び出しは以下のようになる。

task.ContinueWith((t) =>
{
  Console.WriteLine();
  Console.WriteLine("sub task {0} done", t.Id);
});

 ここでラムダ式の引数tは、実際には変数taskと同じ内容を持つ。つまり、値がだぶっている。このような、メソッドMを呼び出す際に「x.M(x)」のように「x」が2回出てくる呼び出しのパターンを、筆者は「明示的なthisポインタ」と呼んでしばしば使うのだが、もちろん1行に同じ内容を示す名前が2つもあるのは美しくない。ラムダ式を使えば、実はラムダ式内から変数taskは参照できてしまうのだ。

 それにもかかわらず、このパターンを使うのはリスト2を見て分かるとおり、ラムダ式を使わないで通常のメソッドを記述した際、変数taskにアクセスできなくなるからだ。

 しかし、そうまでして継承を減らしてデリゲート型で拡張性を持たせるような態度は、決して多数派ではないだろうと思っていた。だから、こうして同じような設計のクラスを、しかも.NET Frameworkの中に見たときは目が点になったのである。意外と筆者は、勝手に進んでいるようでトレンドを外していないらしい。

 

 INDEX
  C# 4入門
  第3回 TaskクラスとPLINQ(Parallel LINQ)
  1.天は自らタスクるものをタスク/Taskクラスの基本的な使い方
    2.待てといわれて待つヤツがいるぞ/PLINQ(Parallel Linq)
    3.PLINQのワナ/まとめ
 
インデックス・ページヘ  「C# 4入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間