47

私は現在、長期実行メソッドをキャンセル可能にするための改良を行っています。System.Threading.Tasks.CancellationToken を使用して実装する予定です。

私たちのメソッドは通常、いくつかの長時間実行されるステップを実行します (コマンドをハードウェアに送信してから、ほとんどの場合ハードウェアを待機します)。

void Run()
{
    Step1();
    Step2();    
    Step3();
}

キャンセルに関する私の最初の(おそらくばかげた)考えは、これを次のように変換します

bool Run(CancellationToken cancellationToken)
{
    Step1(cancellationToken);

    if (cancellationToken.IsCancellationRequested)
        return false;

    Step2(cancellationToken);

    if (cancellationToken.IsCancellationRequested)
        return false;    

    Step3(cancellationToken);

    if (cancellationToken.IsCancellationRequested)
        return false;

    return true;
}

率直に言って恐ろしく見えます。この「パターン」は、単一のステップ内でも続きます (そして、それらは必然的にすでにかなり長くなっています)。推奨されていないことはわかっていますが、これにより Thread.Abort() はかなりセクシーに見えます。

多くのボイラープレート コードの下にあるアプリケーション ロジックを隠さない、これを達成するためのよりクリーンなパターンはありますか?

編集

ステップの性質の例として、Runメソッドは次のように読むことができます。

void Run()
{
    GiantRobotor.MoveToBase();
    Oven.ThrowBaguetteTowardsBase();    
    GiantRobotor.CatchBaguette();
    // ...
}

連携するために同期する必要があるさまざまなハードウェア ユニットを制御しています。

4

8 に答える 8

33

ステップがメソッド内のデータフローに関して何らかの形で独立しているが、並行して実行できない場合は、次のアプローチの方が読みやすい場合があります。

void Run()
{
    // list of actions, defines the order of execution
    var actions = new List<Action<CancellationToken>>() {
       ct => Step1(ct),
       ct => Step2(ct),
       ct => Step3(ct) 
    };

    // execute actions and check for cancellation token
    foreach(var action in actions)
    {
        action(cancellationToken);

        if (cancellationToken.IsCancellationRequested)
            return false;
    }

    return true;
}

ステップを小さな単位に分割できるためキャンセル トークンが必要ない場合は、より小さなリスト定義を作成することもできます。

var actions = new List<Action>() {
    Step1, Step2, Step3
};
于 2013-11-05T14:45:47.197 に答える
23

続きはどうする?

var t = Task.Factory.StartNew(() => Step1(cancellationToken), cancellationToken)
   .ContinueWith(task => Step2(cancellationToken), cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current)
   .ContinueWith(task => Step3(cancellationToken), cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current);
于 2013-11-05T15:09:49.207 に答える
4

短縮版:

コールをクリティカル セクションとlock()同期するために使用します。Thread.Abort()


バージョンを説明しましょう:

通常、スレッドを中止するときは、次の 2 種類のコードを考慮する必要があります。

  • 終了しても気にしない長時間実行されるコード
  • 絶対にコースを実行する必要があるコード

最初のタイプは、ユーザーが中止を要求したかどうかは気にしないタイプです。たぶん、私たちは千億まで数えていたのに、彼はもう気にしませんか?

のようなセンチネルを使用する場合CancellationToken、重要でないコードの反復ごとにそれらをテストすることはほとんどないでしょうか?

for(long i = 0; i < bajillion; i++){
    if(cancellationToken.IsCancellationRequested)
        return false;
    counter++;
}

とても醜い。したがって、これらのケースでThread.Abort()は、天の恵みです。

Thread.Abort()残念ながら、一部の人が言うように、絶対に実行する必要があるアトミック コードのために使用できません。コードはすでにあなたのアカウントからお金を差し引いています。次に、トランザクションを完了し、お金をターゲット アカウントに転送する必要があります。お金がなくなるのが好きな人はいません。

幸いなことに、私たちはこの種のことを助けるために相互排除を持っています. そして、C# はそれをきれいにします:

//unimportant long task code
lock(_lock)
{
    //atomic task code
}

そして他の場所

lock(_lock) //same lock
{
    _thatThread.Abort();
}

ロックの数は常に<=センチネルの数になります。これは、重要でないコードにもセンチネルが必要なためです (より速くアボートするため)。これにより、センチネル バージョンよりもコードがわずかにきれいになりますが、重要でないことを待つ必要がないため、アボートも改善されます。


また、ブロックThreadAbortExceptionであっても、どこでも上げることができることに注意してください。finallyこの予測不可能性は物議をThread.Abort()かもします。

try-catch-finallyロックを使用すると、ブロック全体をロックするだけでこれを回避できます。クリーンアップfinallyが不可欠な場合は、ブロック全体をロックできます。tryブロックは通常、できる限り短く、できれば 1 行で作成されるため、不要なコードをロックすることはありません。

あなたが言うように、これはThread.Abort()悪を少し減らします。ただし、現在ロックしているため、UI スレッドで呼び出したくない場合があります。

于 2013-11-05T15:30:43.827 に答える
2

これを処理する標準的な組み込みの方法を誰も提案していないことにちょっと驚いています。

bool Run(CancellationToken cancellationToken)
{        
    //cancellationToke.ThrowIfCancellationRequested();

    try
    {
        Step1(cancellationToken);
        Step2(cancellationToken);
        Step3(cancellationToken);
    }
    catch(OperationCanceledException ex)
    {
        return false;
    }

    return true;
}

void Step1(CancellationToken cancellationToken)
{
    cancellationToken.ThrowIfCancellationRequested();
    ...
}

通常は、チェックをより深いレベルにプッシュすることに依存したくありませんが、この場合、ステップは既に CancellationToken を受け入れており、とにかくチェックを行う必要があります (CancellationToken を受け入れる重要なメソッドと同様に)。

これにより、必要に応じてチェックを細かくまたは非細かくすることもできます。つまり、長時間実行/集中的な操作の前に行うことができます。

于 2013-11-11T22:11:35.183 に答える
1

リファクタリングが許可されている場合は、メソッドが 1 つになるように Step メソッドをリファクタリングできますStep(int number)

その後、1 から N までループして、キャンセル トークンが 1 回だけ要求されているかどうかを確認できます。

bool Run(CancellationToken cancellationToken) {
    for (int i = 1; i < 3 && !cancellationToken.IsCancellationRequested; i++) 
        Step(i, cancellationToken);

    return !cancellationToken.IsCancellationRequested;
}

または、同等に: (どちらでも構いません)

bool Run(CancellationToken cancellationToken) {
    for (int i = 1; i < 3; i++) {
        Step(i, cancellationToken);
        if (cancellationToken.IsCancellationRequested)
            return false;
    }
    return true;
}
于 2013-11-05T14:46:19.510 に答える