並列処理のキャンセル

Last Updated 2011/09/21



CancellationToke 構造体

CancellationToke 構造体は、並列処理をキャンセルするための情報を保持するものですが、情報を保持するだけでキャンセルを直接実行するための機能はありません。それを実行するのは、CancellationTokeSource クラスです。したがって、CancellationToke 構造体と CancellationTokeSource クラスとは一体のものと考えなければなりません。続きは、CancellationTokeSource クラスをご覧ください。


CancellationTokeSource クラス


AggregateException クラス

この例外クラスは、並列処理のために .NET Framework 4.0 に追加されたもので、ほかの例外クラスとはチョット趣を異にします。並列処理では通常複数のスレッドで処理が進みますから、何か不都合があればアチコチのスレッドがいろいろな例外をスローします。そこで、AggregateException クラスに発生した例外を集めます。その結果は、このクラスの InnerExceptions プロパティから参照するコレクションに格納されます。また、ほかの例外クラスにはない次の 2 つのメソッドを提供します。


メソッドの内容については NETClass を見てもらうとしてその使い方を説明します。

Handle メソッド
  try
  {
    ....
  }
  catch (AggregateException ae)
  {
    ae.Handle((x) =>
    {
      MessageBox.Show(x.Message);
      return true; // ここで現在の例外を処理済みとしておくと、以後の処理で例外は発生しない
     });
  }
Flatten メソッド
  try
  {
    .... 
  }
  catch (AggregateException ae)
  {
    foreach (var aef in ae.Flatten().InnerExceptions)
    {
      if (aef is OperationCanceledException)
        return;
      else
        throw;
    }
  }

特定の条件のときに並列処理を中止する

特定の条件を満足するときとは、たとえば、巨大なデータから指定のデータの検索に成功したとき、それ以上の検索は不要ですから検索を中止することになりますが、このようなときです。

以下のコード例は、.NET Framework SDK の中にあるサンプルコードに手を入れたものです。Parallel.For メソッドには CancellationToken 型を設定する構文がありませんので、ParallelOptions クラスを使います。
private void button1_Click(object sender, RoutedEventArgs e)
{
  CancellationTokenSource tokenSource = new CancellationTokenSource();
  ParallelOptions options = new ParallelOptions();
  options.CancellationToken = tokenSource.Token;

  try
  {
    ParallelLoopResult loopResult = Parallel.For(0, 10, options, (i, loopState) =>
      {
        Debug.WriteLine(String.Format("開始 ThreadId={0}, i={1}", Thread.CurrentThread.ManagedThreadId, i));

        // i = 2 のとき、ループを中止する
        if (i == 2)
        {
          tokenSource.Cancel();
        }

        // ここが実際にループ処理の対象となる時間のかかる処理
        for (int j = 0; j < 10; ++j)
        {
          Thread.Sleep(200);

          // キャンセルしたとき、ShouldExitCurrentIteration は true に設定されるので
          // それをチェックし、true なら中止する
          if (loopState.ShouldExitCurrentIteration)
            return;
        }

        Debug.WriteLine(String.Format("終了 ThreadId={0}, i={1}", Thread.CurrentThread.ManagedThreadId, i));
      });

    // tokenSource.Cancel(); の行をコメントアウトすると、このコードに達する
    if (loopResult.IsCompleted)
    {
      Debug.WriteLine("ループ処理がすべて終了した(ただし、これは起こらない)");
    }
  }
  catch (AggregateException ex)
  {
    Debug.WriteLine(String.Format("AggregateException 例外(ただし、これは起こらない。\n{0}", ex));
  }
  catch (OperationCanceledException ex)
  {
    Debug.WriteLine(String.Format("ループ処理がキャンセルされた。\n{0}", ex));
  }
}

実行結果:

開始 ThreadId=9, i=0
開始 ThreadId=10, i=2
'CancelTest.vshost.exe' (マネージ (v4.0.30319)): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\mscorlib.resources\v4.0_4.0.0.0_ja_b77a5c561934e089\mscorlib.resources.dll' が読み込まれました
'System.OperationCanceledException' の初回例外が mscorlib.dll で発生しました。
ループ処理がキャンセルされた。
System.OperationCanceledException: 操作はキャンセルされました。
   場所 System.Threading.Tasks.Parallel.ForWorker[TLocal](Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action`1 body, Action`2 bodyWithState, Func`4 bodyWithLocal, Func`1 localInit, Action`1 localFinally)
   場所 System.Threading.Tasks.Parallel.For(Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action`2 body)
   場所 CancelTest.MainWindow.mnuTest3_Click(Object sender, RoutedEventArgs e) 場所 D:\NET4\Execute\Parallel\CancelTest\MainWindow.xaml.cs:行 118

次は、タスクを使う並列処理をキャンセルする場合です。

並列処理をキャンセルする基本は、CancellationTask 型の ThrowIfCancellationRequested メソッドを呼び出して、OperationCanceledException 例外を発生させることです。try ... catch 文でこの例外を拾って、その後の処理を決定します。


UI から並列処理を中止する

コンソールアプリケーションであれば ThreadPool スレッドしかありませんので、簡単ですが、UI アプリケーションの場合、UI コントロールは UI スレッドでのみ作成可能で、UI スレッドからでしかアクセスできません。

UI アプリケーションにおいて並列処理をキャンセルするにはボタンコントロールなどの UI コントロールを使ってキャンセルの通知をすることになります。そこで、タスクが UI スレッド上で動作するようにする必要があります。

TaskScheduler クラスの static な FromCurrentSynchronizationContext メソッドは、UI スレッドに割り当てられたデフォルトの TaskScheduler オブジェクトを返します。

−以上−