ASP.NET Core MVC - 時間がかかるアクションメソッドをキャンセルできるようにする

ASP.NET Core 2.2からサポートされているインプロセスホスティングモデル。その特徴の1つをDocsから引用します。

クライアントの切断が検出されます。

クライアントが切断されると、HttpContext.RequestAbortedキャンセルトークンが取り消されます。

docs.microsoft.com

今回はこの機能を使って時間がかかるアクションメソッドのキャンセルを試していきます。

「クライアントの切断」というのはChromeFirefoxの×ボタンやEscキーで発生するようです。ウィンドウやタブの×ボタンではなく、通信中に更新ボタンと同じ位置に現れる×ボタンのことを言っています。

Edgeでも同じことを試してみましたが、どうも発生しない様子でした。

確認した各ブラウザのバージョンです。OSはWindows 10です。

ブラウザのウィンドウやタブを閉じたりとか、XMLHttpRequestの通信なども試していませんがどうなんでしょう。 このあたりの仕様はどこかにあるのかな。(調べずに言ってます。)

すべてのブラウザでキャンセルできるものではないという前提で話を進めていきます。

CancellationTokenをバインドする

アクションをキャンセルできるようにするには、アクションメソッドの引数にCancellationTokenを追加します。CancellationTokenModelBinderクラスがHttpContext.RequestAbortedをバインドしてくれるようです。

後はそのCancellationTokenをチェックするだけです。非同期メソッドの引数に渡していくというか。

public class SampleController : Controller {
    private readonly ILogger _logger;

    public SampleController(ILogger<SampleController> logger) {
        _logger = logger;
    }

    // キャンセルしたいアクション
    // CancellationTokenをバインドする
    public async Task<IActionResult> CancelTest(CancellationToken token) {
        try {
            _logger.LogInformation("Heavy task started.");

            // 何か時間がかかる処理
            // バインドしたCancellationTokenを引数に渡す
            await Task.Delay(TimeSpan.FromSeconds(10), token);

            _logger.LogInformation("Heavy task completed.");
        } catch (TaskCanceledException _) {
            // ブラウザのEscキーでキャンセルされる
            _logger.LogInformation("Heavy task canceled.");
        }

        return new EmptyResult();
    }
}

ChromeFirefoxでアクションのURLにアクセスしてみましょう。

そして途中でEscキーを押すと、アクションメソッドの中のTaskがキャンセルされることを確認できます。

// 出力されるログ
Heavy task started.
Heavy task canceled.

参考

ホスティングモデル自体についてはこのあたりが参考になると思います。

docs.microsoft.com