.NET Core使用 CancellationToken 取消API請求

您是否曾經訪問過一個網站,它需要很長時間加載,最終你敲擊 F5 重新加載頁面。

即使用戶刷新了瀏覽器取消了原始請求,而對於服務器來說,API也不會知道它正在計算的值將在結束時被丟棄,刷新五次,服務器將觸發 5 個請求。

爲了解決這個問題,ASP.NET Core 爲 Web 服務器提供了一種機制,就是CancellationToken.

用戶取消請求時,你可以使用HttpContext.RequestAborted訪問,您也可以使用依賴注入將其自動注入到您的操作中。

 

長時間運行的任務請求

現在我們假設您有一個 API 操作,在向用戶發送響應之前可能需要一些時間才能完成。

在處理該操作時,用戶可以直接取消請求,或刷新頁面(這會有效地取消原始請求,並啓動新請求)。

[HttpGet(Name = "get")]
public async Task<string> GetAsync()
{
    try
    {
        _logger.LogInformation("request in");
        await Task.Delay(5 * 1000);
        _logger.LogInformation("request end");
    }
    catch (Exception ex)
    {
        _logger.LogInformation("request ex");
    }
    return "ok";
}

如果用戶在請求中途刷新瀏覽器,那麼瀏覽器永遠不會收到第一個請求的響應,但在server端可以看到,操作方法執行完成兩次。

這是否是正確將取決於您的應用程序。

如果請求修改某些業務的狀態,那麼您可能不希望在方法中途停止執行。如果請求沒有副作用,那麼您可能希望儘快停止(可能很昂貴)操作。

用戶取消請求時,你可以使用HttpContext.RequestAborted訪問,您也可以使用依賴注入將其自動注入到您的操作中。

 

CancellationTokens取消不必要的請求

以下代碼顯示瞭如何通過將 CancellationTokenSource 注入到操作方法中,並通過其取消不必要的操作。

[HttpGet(Name = "get")]
public async Task<string> GetAsync(CancellationToken cancellationToken)
{
    try
    {
        _logger.LogInformation("request in");
        await Task.Delay(5 * 1000,cancellationToken);
        _logger.LogInformation("request end");
    }
    catch (Exception ex)
    {
        _logger.LogInformation("request ex");
    }
    return "ok";
}

通過這個改變,我們可以再次測試我們的場景。

我們發出一個初始請求,然後我們重新加載頁面。正如您從下面的日誌中看到的,第一個請求不會繼續執行。

用戶刷新瀏覽器取消請求後不久,原始請求就會中止,並TaskCancelledException通過 API 過濾器管道傳播回來,並備份中間件管道。

根據您的場景,您可能能夠依靠此類框架方法來檢查 的狀態CancellationToken,或者您可能需要自己監視取消請求。

 

過濾器捕獲異常

您可以通過以上try catch 捕獲,或者通過一個過濾器統一監視此異常。

public class OperationCancelledExceptionFilter : ExceptionFilterAttribute
{
    private readonly ILogger _logger;

    public OperationCancelledExceptionFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<OperationCancelledExceptionFilter>();
    }
    public override void OnException(ExceptionContext context)
    {
        if (context.Exception is OperationCanceledException)
        {
            _logger.LogInformation("Request was cancelled");
            context.ExceptionHandled = true;
            context.Result = new StatusCodeResult(400);
        }
    }
}


builder.Services.AddControllers(options =>
{
    options.Filters.Add<OperationCancelledExceptionFilter>();
});

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章