web-dev-qa-db-ja.com

キャンセルトークンのリンク

私は、サービスが完全にシャットダウンできるように、渡されるキャンセルトークンを使用しています。このサービスには、他のサービスへの接続を試行し続けるロジックがあるため、トークンは、別のスレッドで実行されているこれらの再試行ループから抜け出すための良い方法です。私の問題は、内部再試行ロジックを持つサービスを呼び出す必要がありますが、再試行が失敗した場合は、設定された期間後に戻る必要があるということです。これを行うタイムアウトを備えた新しいキャンセルトークンを作成したいと思います。これの問題は、新しいトークンが「マスター」トークンにリンクされていないため、マスタートークンがキャンセルされても、タイムアウトするか接続が確立されて戻るまで、新しいトークンは引き続き有効です。マスタートークンがキャンセルされると、新しいトークンもキャンセルされるように、2つのトークンをリンクします。 CancellationTokenSource.CreateLinkedTokenSourceメソッドを使用してみましたが、新しいトークンがタイムアウトになると、マスタートークンもキャンセルされました。トークンで行う必要があることを行う方法はありますか、それとも再試行ロジックの変更が必要になりますか(おそらくこれを簡単に行うことができないでしょう)

これが私がやりたいことです:

マスタートークン–サービスが完全にシャットダウンできるように、さまざまな関数を渡して渡されます。一時トークン–単一の関数に渡され、1分後にタイムアウトするように設定されています

マスタートークンをキャンセルする場合は、一時トークンもキャンセルする必要があります。

一時トークンの有効期限が切れた場合、マスタートークンを取り消すことはできません。

37
Retrocoder

CancellationTokenSource.CreateLinkedTokenSourceを使用したい。 「親」と「子」CancellationTokenSourceesを持つことができます。以下に簡単な例を示します。

var parentCts = new CancellationTokenSource();
var childCts = CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token);

childCts.CancelAfter(1000);
Console.WriteLine("Cancel child CTS");
Thread.Sleep(2000);
Console.WriteLine("Child CTS: {0}", childCts.IsCancellationRequested);
Console.WriteLine("Parent CTS: {0}", parentCts.IsCancellationRequested);
Console.WriteLine();

parentCts.Cancel();
Console.WriteLine("Cancel parent CTS");
Console.WriteLine("Child CTS: {0}", childCts.IsCancellationRequested);
Console.WriteLine("Parent CTS: {0}", parentCts.IsCancellationRequested);

期待どおりの出力:

子CTSをキャンセル
子供CTS:真
親CTS:誤り

親CTSをキャンセル
子供CTS:真
親CTS:真

63
i3arnon

CancellationTokenではなくCancellationTokenSourceのみを使用している場合でも、リンクされたキャンセルトークンを作成することは可能です。単純にRegisterメソッドを使用して、(擬似)子のキャンセルをトリガーします。

var child = new CancellationTokenSource();
token.Register(child.Cancel);

CancellationTokenSourceで通常行うことなら何でもできます。たとえば、しばらくするとキャンセルでき、以前のトークンを上書きすることもできます。

child.CancelAfter(cancelTime);
token = child.Token;
4
John Gietzen

i3arnonは既に回答済み なので、CancellationTokenSource.CreateLinkedTokenSource()を使用してこれを行うことができます。タスク全体のキャンセルとタスク全体のキャンセルなしの子タスクのキャンセルを区別したい場合に、このようなトークンの使用方法のパターンを示したいと思います。

_async Task MyAsyncTask(
    CancellationToken ct)
{
    // Keep retrying until the master process is cancelled.
    while (true)
    {
        // Ensure we cancel ourselves if the parent is cancelled.
        ct.ThrowIfCancellationRequested();

        var childCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
        // Set a timeout because sometimes stuff gets stuck.
        childCts.CancelAfter(TimeSpan.FromSeconds(32));
        try
        {
            await DoSomethingAsync(childCts.Token);
        }
        // If our attempt timed out, catch so that our retry loop continues.
        // Note: because the token is linked, the parent token may have been
        // cancelled. We check this at the beginning of the while loop.
        catch (OperationCancelledException) when (childCts.IsCancellationRequested)
        {
        }
    }
}
_

一時トークンの有効期限が切れた場合、マスタートークンを取り消すことはできません。

MyAsyncTask()の署名はCancellationTokenではなくCancellationTokenSourceを受け入れることに注意してください。このメソッドはCancellationTokenのメンバーにのみアクセスできるため、誤ってマスター/親トークンをキャンセルすることはできません。マスタータスクのCancellationTokenSourceができるだけ少ないコードから見えるようにコードを整理することをお勧めします。ほとんどの場合、これは、CancellationTokenSourceへの参照を共有する代わりに、メソッドに_CancellationTokenSource.Token_を渡すことで実行できます。

私は調査していませんが、CancellationTokenにアクセスせずにCancellationTokenSourceを強制的にキャンセルするためのリフレクションなどの方法があるかもしれません。できれば不可能ですが、それが可能であれば、それは悪い習慣と見なされ、一般に心配することではありません。

1
binki