none
マルチスレッド上の並列処理(Parallel)の強制終了について RRS feed

  • 質問

  • Visual C# 2010(.NET Framework 4.0)で並列処理をするプログラムを組んでいます。

    private void OtherThread()
    {
    	try {
    		List<string> files = new List<string>();
    		files.Add("aa");
    		files.Add("bb");
    		files.Add("cc");
    
    		ParallelOptions po = new ParallelOptions();
    		po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;
    		Parallel.ForEach<string>(convfiles, po, ConvDirs);
    	} catch (ThreadAbortException) {
    	}
    }
    
    private void ConvDirs(string fname, ParallelLoopState pls)
    {
    	// 並列処理
    }
    

    このようなプログラムで、Thread.Abort()によって、スレッドを止めようとすると、並列処理の内のひとつしか終了せず、残りの処理は続行されてしまいます。

    Thread.Abort()で、すべての処理を終わらせるには、また、Parallelとマルチスレッドを併用する際には、どのように気をつけたら良いでしょうか。

    2011年4月14日 10:43

回答

  • スレッドは基本的に外部から強制的に終了させる物ではありません。止める側はあくまで停止要求を出すだけであり、スレッド側がその要求を見て自主的に終了させる物です。そうでないと整合性が正しくとれなくなります。

    このキャンセル要求→自主停止というのを協調的と言ったりしますが、そのための仕組みとして .NET 4 でキャンセルフレームワークというものが用意されました。キャンセル要求側は CancellationTokenSource オブジェクトを作成し、スレッドに CancellationToken を渡します。CancellationTokenSource の Cancel を呼び出すと、スレッド側に渡された CancellationToken はキャンセル要求状態になります。スレッド側はこの要求状態を定期的に確認し、キャンセルが要求されていれば処理を終了させます。

    .NET 4 で追加された Task や Parallel はもちろんキャンセルフレームワークに対応しており、以下のページにその例が挙げられています。

    方法: Parallel.For または ForEach ループを取り消す

    この例では直接 ParallelOptions.CancellationToke を参照していますが、Parallel.ForEach に渡す Action<T> が独立したメソッドの場合 CancellationToken を参照できないので、代わりに ParallelLoopState の ShouldExitCurrentIteration を参照して終了させるべきかどうかの判断をおこないます。

    // なんで ParallelLoopState に CancellationToken が渡ってこないんだろう?

    なお、Paralell.ForEach をキャンセルするという場合二つのキャンセルすべき事象が存在します。一つはループそのものであり、もう一つはそのループ内の各処理それぞれキャンセルです。

    この二つの事象のおうち、CancellationTokenSource.Cancel の呼び出しによって直接キャンセルされるのはループのみということになっています。すでに開始されている処理はその完了まで待ってから、最終的に OperationCanceledException が投げられます。

    各処理は、上述のように定期的に ShouldExitCurrentIteration をチェックして終了判定させる必要があります。

    • 回答としてマーク Tank2005 2011年4月15日 4:53
    2011年4月14日 11:35

すべての返信

  • スレッドは基本的に外部から強制的に終了させる物ではありません。止める側はあくまで停止要求を出すだけであり、スレッド側がその要求を見て自主的に終了させる物です。そうでないと整合性が正しくとれなくなります。

    このキャンセル要求→自主停止というのを協調的と言ったりしますが、そのための仕組みとして .NET 4 でキャンセルフレームワークというものが用意されました。キャンセル要求側は CancellationTokenSource オブジェクトを作成し、スレッドに CancellationToken を渡します。CancellationTokenSource の Cancel を呼び出すと、スレッド側に渡された CancellationToken はキャンセル要求状態になります。スレッド側はこの要求状態を定期的に確認し、キャンセルが要求されていれば処理を終了させます。

    .NET 4 で追加された Task や Parallel はもちろんキャンセルフレームワークに対応しており、以下のページにその例が挙げられています。

    方法: Parallel.For または ForEach ループを取り消す

    この例では直接 ParallelOptions.CancellationToke を参照していますが、Parallel.ForEach に渡す Action<T> が独立したメソッドの場合 CancellationToken を参照できないので、代わりに ParallelLoopState の ShouldExitCurrentIteration を参照して終了させるべきかどうかの判断をおこないます。

    // なんで ParallelLoopState に CancellationToken が渡ってこないんだろう?

    なお、Paralell.ForEach をキャンセルするという場合二つのキャンセルすべき事象が存在します。一つはループそのものであり、もう一つはそのループ内の各処理それぞれキャンセルです。

    この二つの事象のおうち、CancellationTokenSource.Cancel の呼び出しによって直接キャンセルされるのはループのみということになっています。すでに開始されている処理はその完了まで待ってから、最終的に OperationCanceledException が投げられます。

    各処理は、上述のように定期的に ShouldExitCurrentIteration をチェックして終了判定させる必要があります。

    • 回答としてマーク Tank2005 2011年4月15日 4:53
    2011年4月14日 11:35
  • > なんで ParallelLoopState に CancellationToken が渡ってこないんだろう?

    Cancel Token は、ループの published な操作を提供するためのもので、ParallelLoopState はループの internal な操作を確認したりするものだから…でしょう。意味的にまったく違うものですので、ここに Cancel Token があったら不具合の原因にしかならないと思われます。シンプルなループを呼び出す場合等、特定の限定的なシチュエーションにおいては便利かもしれませんので、そういった用途に Task Framework を頻用するようであれば、そういったヘルパを準備されるとよいかもしれませんね。

    通常のループ処理でいえば、Cancel Tolen はループの外側にスコープを持つ状態変数で、LoopState はループの内側にスコープを持つ状態変数として表現されていたものだと考えると納得がいくのではないでしょうか。

    並列化が実施されることで、ループの内側の処理がプログラム言語では複数のスコープに分離されてしまうため、それらのループの論理スコープ状態を、すべての並列化スレッドで共有する必要があります。それを提供するのが LoopState ですね。

    for (int i = 0; i < MaxCount; i++) { /* 処理 */ if (/*処理結果*/) { break; // 並列処理だと、この break は // すべてのスレッドに配布されないといけない } }  ↓ TPL でのイメージ
    // これが TPL の Cancel Token 相当
    
    bool exitIfXXXX = false;
    
    for (int i = 0; i < MaxCount; i++)
    
    {
    
     // これが LoopState
    
     bool breakThisLoop = false;
    
    
    
     /** ここから別スレッド **/
    
    
     if (exitIfXXXX) break;
    
     /* 処理 */
    
     if (/*処理結果*/)
    
     {
    
       // 並列処理のため、break を配布する
    
      breakThisLoop = true;
    
     }
    
    
    
     // LoopState を見て抜ける
    
     if (breakThisLoop) break;
    
    
    
     /** ここまで別スレッド **/
    
    }
    
    
    
    

    2011年4月15日 0:07