ASP.NET Core割り込みリクエスト了解

8126 ワード

ASP.NET Coreは何シリーズを知っていますか:全体の紹介とカタログ
本明細書で説明する方法は、Kestrel Serverで管理されているアプリケーションにのみ適用されます.IISとIIS Expressに管理する場合、ASP.NET Core Module(ANCM)はASP.には言わないNET Coreはクライアントの切断時に要求を中止します.しかし喜ばしいことに、ANCMは.NET Core 2.2はこのメカニズムを改善します.

1.引用


時間のかかるActionがあると仮定し、ブラウザが要求応答を返す前にページをリフレッシュすると、ブラウザ(クライアント)にとって前の要求が終了します.サービス側にとってはどうだろう.前のリクエストも自動的に終了しますか?それとも実行を続行しますか?
次に、実例を用いて答えを求めます.

2.実例プレゼンテーション

SlowRequestControllerを作成し、Getリクエストを定義し、Task.Delay(10_000)を介して時間消費動作をシミュレートします.コードは次のとおりです.
public class SlowRequestController : Controller
{
    private readonly ILogger _logger;

    public SlowRequestController(ILogger logger)
    {
        _logger = logger;
    }

    [HttpGet("/slowtest")]
    public async Task Get()
    {
        _logger.LogInformation("Starting to do slow work");

        // slow async action, e.g. call external api
        await Task.Delay(10_000);

        var message = "Finished slow delay of 10 seconds.";

        _logger.LogInformation(message);

        return message;
    }
}

リクエストを開始すると、ページの表示に10 sかかります.実行ログをチェックすると、その出力が予想通りであることがわかります.
最初のリクエストが戻る前にページをリフレッシュしたらどうなりますか?
ログから、リフレッシュ後、最初のリクエストはクライアントでキャンセルされましたが、サービス側は引き続き実行されます.
これにより、MVCのデフォルトの動作を説明できます.ユーザーがブラウザをリフレッシュすると元の要求がキャンセルされますが、MVCは何も知らないので、キャンセルされた要求はサービス側で実行され続け、最終的な実行結果は破棄されます.
これにより、パフォーマンスの浪費が深刻になります.サービス側がユーザが要求を中断したことを感知し、実行に時間がかかるタスクを終了すればよい.
幸い、ASP.NET Core開発チームは、クライアントのリクエストが終了したかどうかを、以下の2つの方法で取得できるように配慮しています.
  • HttpContexRequestAborted属性:
  • を通過
  • 方法注入CancellationTokenパラメータ:
  • if (HttpContext.RequestAborted.IsCancellationRequested)
    {
        // can stop working now
    }
    [HttpGet]
    public async Task GetHardWork(CancellationToken cancellationToken)
    {
        // ...
     
        if (cancellationToken.IsCancellationRequested)
        {
            // stop!
        }
         
        // ...
    }

    この2つの方法は、HttpContext.RequestAbortedcancellationTokenが同じオブジェクトに対応しているため、実際には同じです.
    if(cancellationToken == HttpContext.RequestAborted)
    {
        // this is true!
    }

    次に、cancellationTokenを例に、クライアント要求がサービス側サービスを終了し、終了することをどのように感知するかを見てみましょう.

    3.ActionでCancellationTokenを使用する

    CancellationTokenは、CancellationTokenSourceによって作成された軽量レベルのオブジェクトです.CancellationTokenSourceがキャンセルされると、すべての消費者CancellationTokenに通知されます.
    キャンセルすると、CancellationTokenIsCancellationRequested属性がTrueに設定され、CancellationTokenSourceがキャンセルされたことを示します.
    前の例に戻ると、長期にわたって実行される操作方法があります(たとえば、多くの他のAPIを呼び出して読み取り専用レポートを生成します).これは高価な方法であるため、ユーザーが要求をキャンセルしたときにできるだけ早く操作を停止することを望んでいます.
    次のコードは、actionメソッドにCancellationTokenを注入し、Task.Delayに渡すことによって、サービス側要求を同期的に終了する目的を達成することを示す.
    public class SlowRequestController : Controller
    {
        private readonly ILogger _logger;
    
        public SlowRequestController(ILogger logger)
        {
            _logger = logger;
        }
    
        [HttpGet("/slowtest")]
        public async Task Get(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Starting to do slow work");
    
            // slow async action, e.g. call external api
            await Task.Delay(10_000, cancellationToken);
    
            var message = "Finished slow delay of 10 seconds.";
    
            _logger.LogInformation(message);
    
            return message;
        }
    }

    MVCは、CancellationTokenModelBinderを使用して、アクション内の任意のCancellationTokenパラメータをHttpContext.RequestAbortedに自動的にバインドする.Startup.ConfigureServices()services.AddMvc()またはservices.AddMvcCore()を呼び出すと、CancellationTokenModelBinderモデルバインダが自動的に登録されます.
    この小さな変更により、最初のリクエストが戻る前にページをリフレッシュしようとしましたが、ログから最初のリクエストが完了しないことがわかりました.代わりに、Task.DelayCancellationToken.IsCancellationRequested属性がtrueであることを検出すると、直ちに実行が停止し、TaskCancelledExceptionが放出される.
    簡単に言えば、ユーザはブラウザをリフレッシュし、TaskCancelledException異常を投げ出すことによってサービス側で最初の要求を終了し、この異常は要求パイプを通じて再伝播される.
    このシナリオでは、Task.Delay()CancellationTokenを監視するので、CancellationTokenがキャンセルされたかどうかを手動でチェックする必要はありません.

    4.手動でCancellationTokenの状態をチェックする

    CancellationTokenまたはTask.Delay()のようなHttpClient.SendAsync()をサポートする内蔵メソッドを呼び出している場合は、CancellationTokenに直接転送し、内部メソッドに実際のキャンセルを担当させることができます.他の場合、同期作業を行っている場合があります.これらの作業をキャンセルしたい場合があります.たとえば、会社員のすべてのコミッションを計算するレポートを構築しているとします.従業員一人一人を循環し、販売を遍歴します.
    このレポートを途中で取り消すことができる簡単な解決策は、forループ内のCancellationTokenをチェックし、ユーザが要求を取り消すとループから飛び出します.次の例は、Thread.Sleep()のペアによってシミュレートされる、10回のループおよびいくつかの同期(キャンセル不可)動作を実行することによって表される.各サイクルの開始時にCancellationTokenを調べ,キャンセルすると異常を放出した.これにより、長時間実行される同期タスクを終了できます.
    public class SlowRequestController : Controller
    {
        private readonly ILogger _logger;
    
        public SlowRequestController(ILogger logger)
        {
            _logger = logger;
        }
    
        [HttpGet("/slowtest")]
        public async Task Get(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Starting to do slow work");
    
            for(var i=0; i<10; i++)
            {
                cancellationToken.ThrowIfCancellationRequested();
                // slow non-cancellable work
                Thread.Sleep(1000);
            }
            var message = "Finished slow delay of 10 seconds.";
    
            _logger.LogInformation(message);
    
            return message;
        }
    }

    リクエストをキャンセルすると、ThrowIfCancelletionRequested()の呼び出しがOperationCanceledExceptionに投げ出され、フィルタパイプとミドルウェアパイプに再び伝播します.

    5.ExceptionFilterスナップによる例外のキャンセル


    ExceptionFiltersは、操作方法または操作フィルタで発生した例外を処理するMVCコンセプトです.公式文書を参照できます.
    フィルタは、コントローラレベルと操作レベル、またはグローバルレベルに適用できます.簡単にするために、フィルタを作成し、グローバルフィルタに追加します.
    public class OperationCancelledExceptionFilter : ExceptionFilterAttribute
    {
        private readonly ILogger _logger;
    
        public OperationCancelledExceptionFilter(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger();
        }
        public override void OnException(ExceptionContext context)
        {
            if(context.Exception is OperationCanceledException)
            {
                _logger.LogInformation("Request was cancelled");
                context.ExceptionHandled = true;
                context.Result = new StatusCodeResult(499);
            }
        }
    }
    OnExceptionメソッドを再ロードし、OperationCanceledException異常を特殊に処理することで、キャンセル異常のキャプチャに成功しました.Task.Delay()が投げ出す異常はTaskCancelledExceptionタイプであり、OperationCanceledExceptionのベースクラスであるため、以上のフィルタも正しく捕捉できる.
    次に、フィルタを登録します.
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(options =>
            {
                options.Filters.Add();
            });
        }
    }

    次にテストすると、実行ログには例外情報が含まれていないことがわかりました.代わりに、カスタマイズした情報が含まれています.

    6.最後に


    本稿では,ユーザがブラウザ上の停止または再ロードボタンをクリックすることで,Webアプリケーションの要求を随時取り消すことができることを知っている.実際にはクライアントのリクエストが終了しただけで、サービス側のリクエストはまだ実行されています.簡単に時間がかかる要求には、相手にしなくてもいいです.しかし、時間のかかるタスクでは、パフォーマンスの損失が高いため、無視することはできません.
    どうやって解決しますか?その鍵は、CancellationTokenによってユーザ要求の状態をキャプチャし、必要に応じて対応する処理を行うことである.
    参考資料:CancellationTokens and Aborted ASP.NET Core Requests Using CancellationTokens in ASP.NET Core MVC controllers