1. ホーム
  2. Web プログラミング
  3. ASP.NET
  4. 実用的なヒント

非同期タスクキャンセルと監視のネット実装

2022-01-14 07:02:41

関連する種類

  • CancellationTokenSourceは、主にトークンの作成またはキャンセルに使用される
  • CancellationTokenはトークンの状態をリッスンし、トークンのキャンセル・イベントを登録する
  • OperationCanceledException トークンがキャンセルされたときにスローされる例外で、リスナーが任意にスローできる。

CancellationTokenSource

トークンを作成します。

CancellationTokenSource cts = new CancellationTokenSource()

CancellationToken token=cts;



解約解除トークンです。

cts.Cancel();

キャンセルトークン

トークンキャンセルイベントをリスニングします。

token.Register(() => Console.WriteLine("token cancelled"));


トークンがキャンセルされたかどうかの判定

//returns a bool, true if the token was cancelled
token.IsCancellationRequested

//Throw an exception if the token is cancelled, the internal implementation actually determines IsCancellationRequested
token.ThrowIfCancellationRequested()=>{
 if(token.IsCancellationRequested){
  throw new OperationCanceledException();
 }
}



コード例

以下は、ダウンロードが完了する前にキャンセルされたファイルダウンロードのタスクをシミュレートしたものです。

 public void Run()
 {
     CancellationTokenSource cts = new CancellationTokenSource();

     Task.Run(() =>
              {
                  // wait two seconds and then cancel, simulating a user-initiated cancellation of the download task
                  Thread.Sleep(2000);
                  cts.Cancel();
              Try)

     Try
     {
         var size = DownloadFile(cts.Token);
         Console.WriteLine("File size: " + size);
     }
     catch (OperationCanceledException)
     {
         Console.WriteLine("Download failed");
     }finally{
         cts.Dispose();
     }
     Thread.Sleep(2000);
 }


/// <summary>
/// Simulate downloading a file, it takes five seconds to download the file
/// </summary>
//// <returns></returns>
public int DownloadFile(CancellationToken token)
{
    token.Register(() =>
                   {
                       System.Console.WriteLine("Listening to cancellation event");
                   });

    Console.WriteLine("Started downloading file");
    for (int i = 0; i < 5; i++)
    {
        token.ThrowIfCancellationRequested();
        Console.WriteLine(i.ToString());
        Thread.Sleep(1000);
    Sleep(1000); }
    Console.WriteLine("File download complete");
    return 100;
}



出力結果です。

ファイルのダウンロードを開始
0
1
キャンセルイベントのリスニング
ダウンロードに失敗しました

振り返り

なぜ、CancelationToken と CancelationTokenSource を2つのクラスに分ける必要があるのでしょうか。キャンセルとステータス登録の判定もできるCancelationTokenを直接用意したほうが便利でいいのでは?

実際には、各クラスの設計と実装は、多くの異なる戦略を持つことができます、CTSとCTの2つのクラスが提供するいくつかのパブリックメソッドから見ることができる、CTSはトークンのライフサイクル状態の生成とキャンセルを制御するために使用され、CTのみ聞いて判断に使用できる、トークンの状態を変更することはできません。

つまり、この設計の目的は関心事の分離です。CTの機能を制限し、配信プロセスにおいて制御不能な要因でTokenがキャンセルされることによる混乱を回避するためです。

関連するトークン

上記の例に引き続き、外部制御ファイルからのダウンロード機能の終了を実装する例です。

ファイルのダウンロード機能にタイムアウトの制限を加えたい場合は、外部トークンと内部トークンを関連付けてトークンにすることで、タイムアウトを制御することができます

DownloadFile()関数に以下の変更を加えるだけです。

public int DownloadFile(CancellationToken externalToken)
        {
            //Cancel() function is called after one second of setting the TokenSource through the constructor
            var timeOutToken = new CancellationTokenSource(new TimeSpan(0, 0, 1)).Token;
            using (var linkToken = CancellationTokenSource.CreateLinkedTokenSource(externalToken, timeOutToken))
            {
                Console.WriteLine("Start downloading file");
                for (int i = 0; i < 5; i++)
                {
                    linkToken.Token.ThrowIfCancellationRequested();
                    Console.WriteLine(i.ToString());
                    Thread.Sleep(1000);
                Sleep(1000); }
                Console.WriteLine("File download complete");
                return 100;
            }
        }


この時点で、externalTokenのキャンセルまたはtimeOutTokenのキャンセルのどちらかが、linkTokenのキャンセルイベントをトリガーします。

キャンセルチェンジャートークン

CancellationChangeToken は、主にターゲットの変更を監視するために使用され、ChangeToken と一緒に使用されます。機能的なシナリオとしては、監視対象が変更されると、リスナーが一連の処理を行うという点で、ChangeToken は実際にはイベントと同様に機能するように思われます。

しかし、イベントの場合、リスナーはターゲットの存在を知っている必要があります。つまり、AがBのイベントに登録したい場合、AはBに依存していることになるのです。

CancellationChangeTokenは、CancellationTokenをベースにしており、リスニングされるクラスに直接依存せず、トークンに依存することで実現することができます

CancellationChangeTokenを作成するには。

new CancellationChangeToken(new CancellationTokenSource().Token)

トークンの変更をリスニングする

new CancellationChangeToken(cts.Token).RegisterChangeCallback(obj => Console.WriteLine("token change"), null);



CancellationChangeTokenは、CancellationTokenをレイヤーで包むだけです。registerChangeCallbackは、CancellationTokenのIsCancellationRequested状態をリスニングして終了します。

つまり、ここに書かれているコードでは、内部変更のたびにコールバック・イベントをトリガーすることができないのです。

なぜなら、CTは一度だけCancelし、対応するリスナーは一度だけ実行されるからです。複数のリスナーはできません

継続的に変更をリッスンするためには、次の2つのアクションを行う必要があります。

  • Cancel後にTokenを再初期化させる
  • 各 Cancel コールバックの後に新しい Token を再リスト化する。

まずは以下のコードから。表示パネルに時刻が変わるたびに更新するよう通知する表示を実装しています。

public void Run()
{
    var bjDate = new BeijingDate();
    DisplayDate(bjDate.GetChangeToken, bjDate.GetDate);
    Thread.Sleep(50000);
}

public void DisplayDate(Func<IChangeToken> getChangeToken, Func<DateTime> getDate)
{
    ChangeToken.OnChange(getChangeToken, () => Console.WriteLine("Current Time: " + getDate())));
}

public class BeijingDate
{
    private CancellationTokenSource cts;
    private DateTime date;
    public BeijingDate()
    {
        cts = new CancellationTokenSource();
        var timer = new Timer(TimeChange, null, 0, 1000);
    }

    private void TimeChange(object state)
    {
        date = DateTime;
        var old = cts;
        cts = new CancellationTokenSource();
        old.Cancel();
    }

    public DateTime GetDate() => date;
    public CancellationChangeToken GetChangeToken()
    {
        return new CancellationChangeToken(cts.Token);
    }
}



TimeChange()で時間を変更し、Tokenをリセットして古いものをキャンセルした

ChangeToken.OnChangeでDisplayDateに対応するTokenを取得し、それをリッスンする。

DisplayData関数をBeijingDateクラスからデカップリングする。

ChangeToken.OnChange この関数は、トークンを取得するデリゲートと、トークンのキャンセル・イベントに応答するデリゲートの 2 つのパラメータを取ります。

Tokenキャンセルイベントが処理されるたびに、最初のデリゲートを再コールしてTokenを取得し、その時までに新しいTokenを生成し、最終的に継続的な監視を可能にします。

ネットでは、スクリプトハウスの過去の記事を検索するか、以下の関連記事を引き続き閲覧して、スクリプトハウスからのサポートをご確認ください!