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はこのメカニズムを改善します.
時間のかかるActionがあると仮定し、ブラウザが要求応答を返す前にページをリフレッシュすると、ブラウザ(クライアント)にとって前の要求が終了します.サービス側にとってはどうだろう.前のリクエストも自動的に終了しますか?それとも実行を続行しますか?
次に、実例を用いて答えを求めます.
リクエストを開始すると、ページの表示に10 sかかります.実行ログをチェックすると、その出力が予想通りであることがわかります.
最初のリクエストが戻る前にページをリフレッシュしたらどうなりますか?
ログから、リフレッシュ後、最初のリクエストはクライアントでキャンセルされましたが、サービス側は引き続き実行されます.
これにより、MVCのデフォルトの動作を説明できます.ユーザーがブラウザをリフレッシュすると元の要求がキャンセルされますが、MVCは何も知らないので、キャンセルされた要求はサービス側で実行され続け、最終的な実行結果は破棄されます.
これにより、パフォーマンスの浪費が深刻になります.サービス側がユーザが要求を中断したことを感知し、実行に時間がかかるタスクを終了すればよい.
幸い、ASP.NET Core開発チームは、クライアントのリクエストが終了したかどうかを、以下の2つの方法で取得できるように配慮しています. を通過方法注入
この2つの方法は、
次に、
キャンセルすると、
前の例に戻ると、長期にわたって実行される操作方法があります(たとえば、多くの他のAPIを呼び出して読み取り専用レポートを生成します).これは高価な方法であるため、ユーザーが要求をキャンセルしたときにできるだけ早く操作を停止することを望んでいます.
次のコードは、actionメソッドに
MVCは、
この小さな変更により、最初のリクエストが戻る前にページをリフレッシュしようとしましたが、ログから最初のリクエストが完了しないことがわかりました.代わりに、
簡単に言えば、ユーザはブラウザをリフレッシュし、
このシナリオでは、
このレポートを途中で取り消すことができる簡単な解決策は、forループ内の
リクエストをキャンセルすると、
ExceptionFiltersは、操作方法または操作フィルタで発生した例外を処理するMVCコンセプトです.公式文書を参照できます.
フィルタは、コントローラレベルと操作レベル、またはグローバルレベルに適用できます.簡単にするために、フィルタを作成し、グローバルフィルタに追加します.
次に、フィルタを登録します.
次にテストすると、実行ログには例外情報が含まれていないことがわかりました.代わりに、カスタマイズした情報が含まれています.
本稿では,ユーザがブラウザ上の停止または再ロードボタンをクリックすることで,Webアプリケーションの要求を随時取り消すことができることを知っている.実際にはクライアントのリクエストが終了しただけで、サービス側のリクエストはまだ実行されています.簡単に時間がかかる要求には、相手にしなくてもいいです.しかし、時間のかかるタスクでは、パフォーマンスの損失が高いため、無視することはできません.
どうやって解決しますか?その鍵は、
参考資料:CancellationTokens and Aborted ASP.NET Core Requests Using CancellationTokens in ASP.NET Core MVC controllers
本明細書で説明する方法は、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つの方法で取得できるように配慮しています.
HttpContex
のRequestAborted
属性:CancellationToken
パラメータ:if (HttpContext.RequestAborted.IsCancellationRequested)
{
// can stop working now
}
[HttpGet]
public async Task GetHardWork(CancellationToken cancellationToken)
{
// ...
if (cancellationToken.IsCancellationRequested)
{
// stop!
}
// ...
}
この2つの方法は、
HttpContext.RequestAborted
とcancellationToken
が同じオブジェクトに対応しているため、実際には同じです.if(cancellationToken == HttpContext.RequestAborted)
{
// this is true!
}
次に、
cancellationToken
を例に、クライアント要求がサービス側サービスを終了し、終了することをどのように感知するかを見てみましょう.3.ActionでCancellationTokenを使用する
CancellationToken
は、CancellationTokenSource
によって作成された軽量レベルのオブジェクトです.CancellationTokenSource
がキャンセルされると、すべての消費者CancellationToken
に通知されます.キャンセルすると、
CancellationToken
のIsCancellationRequested
属性が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.Delay
がCancellationToken.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