今回は並列処理のループの取り消しの方法について見ていきます。
通常のfor/foreach文のようなループ処理にユーザーによる任意タイミングでのキャンセルを行いたい場合があるように、並列ループにもそのようなニーズがあることは自明なことと思います。.NET Framework 4では、このようなキャンセル処理をいろいろなパターンで統一的に扱えるようにしたキャンセルトークンを用いた手法が提供されています。
サンプル
以下に簡単なサンプルを示します。
using System; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Sample09_CancelLoop { class Program { static void Main() { var source = new CancellationTokenSource(); var thread = new Thread(DoCancelableAction); thread.Start(source.Token); if (Console.ReadKey().Key == ConsoleKey.C) { Console.Clear(); Console.WriteLine("press cancel key"); source.Cancel(); } } static void DoCancelableAction(object parameter) { try { var option = new ParallelOptions(){ CancellationToken = (CancellationToken)parameter }; Parallel.ForEach(Enumerable.Range(0, 1000), option, value => { Thread.Sleep(100); option.CancellationToken.ThrowIfCancellationRequested(); }); } catch (AggregateException) { //----- 並列処理中からの例外ですが、ここには入りません。 //----- キャンセルの例外は特別扱いされます。 } catch (OperationCanceledException e) { Console.WriteLine(e.Message); } } } } //----- 結果 (例) /* press cancel key 操作はキャンセルされました。 */
CancellationTokenSourceはキャンセルを行うためオブジェクトです。これから生成されたトークンに対してキャンセルを要求することができます。上記の例では、コンソール画面に 'c' キーが押されたときにキャンセルが要求されるようになっています。キャンセル要求を受け取った場合に、OperationCanceledExceptionをthrowしています。ThrowIfCancellationRequestedメソッドは、次のコードと等価です。呼び出すことによる大きなオーバーヘッドはないので、積極的に使ってみると良いと思います。
if (token.IsCancellationRequested) throw new OperationCanceledException(token);
コメントにも書いてありますがOperationCanceledExceptionは特別扱いされる例外で、基本的には集約例外ハンドラには飛ぶことはありませんので注意が必要です。キャンセルトークンを利用したキャンセルの実装にはガイドラインがあり、OperationCanceledExceptionを例外として扱うのではなく連携によるキャンセルとして解釈するようにしなければなりません。そしてこのOperationCanceledExceptionをcatchすることで、キャンセル時に任意の処理を行うことができます。
より詳しくキャンセルについて知る
キャンセルトークンを使用した手法は、上記のようなポーリング (Polling : ループ中にフラグを確認する方法) による手法以外にも、コールバックや待機ハンドルを利用した方法があります。ポーリングは最も簡単な方法のひとつなので、これだけは覚えておきましょう。キャンセルについてより詳しく学習したい場合は、MSDNのキャンセルについての記事を参照すると良いと思います。
次回予告
今回はループのキャンセルについて見てきました。今回まででデータの並列化についてはおしまいです。次回からはタスクの並列化について見ていきたいと思います。