AspNetCore熟練應用CancellationToken,CTO會對你刮目相看
背景
已經有很多文章記錄了 web程式中採用非同步程式設計的優勢和.Net非同步程式設計的用法, 非同步程式設計雖然不能解決查詢資料庫的瓶頸, 但是利用執行緒切換,能最大限度的彈性利用工作執行緒, 提高了web服務的響應能力。
【 9012年了,再不會非同步程式設計你是真老了】
本文要說的是利用非同步程式設計中的取消機制緩解資料庫的查詢瓶頸,開發者只需在 MVC/WebAPI查詢方法體內關注CancllationToken並適時取消非同步任務, 這將大大提高應用的響應能力。
頭腦風暴
想象你請求某網站頁面,該頁面正閃著菊花試圖努力綻放(正在載入),最終你忍不了:
① F5重新整理
② 轉向其他頁面
③ 點選瀏覽器“停止”按鈕
對於可憐的伺服器,使用者快速重新整理5次,伺服器將被迫接受 5倍的工作量,這是因為即使使用者重新整理了瀏覽器(或點選停止按鈕), 雖然取消了原始瀏覽器請求,但是Web伺服器並不Care,仍然按部就班處理進入HTTP pipeline的請求(MVC/WebAPI 中預設行為)。其他②③場景類似。
在非同步程式設計中能向任務發出Cancllation訊號,停止web伺服器一切後端查詢行為。在.NET中,這是使用CancellationToken完成的:
-
取消令牌的例項傳遞到非同步任務
-
非同步任務監視令牌,以檢視請求是否已經被取消。
-
如果請求取消,則應停止執行正在執行的操作。.NET中的大多數非同步方法將具有接受取消令牌的過載。
本文所說的請求是,耗時長的服務端讀取查詢(返回資料但不修改資料的查詢)。取消已修改資料的請求對於用程式可能不是一個好的選擇:
- 是否真的要因使用者導航到應用程式中的另一個頁面而取消儲存?也許可以,但也可能不會。
- 除了資料問題,這也不會提高效能,因為資料庫伺服器將需要回滾該事務,這可能是一項昂貴的操作。
AspNetCore實踐
P1 監測CancellationToken令牌
訪問 MyReallySlowReport頁面,等待5s,最終他們放棄了,去了其他頁面:
所有正在進行的請求都將被取消。
MVC/WebAPI能接受到取消請求的訊號。開發者只需要在Controller Action中新增CancellationToken引數,並在後續行為中監測該取消訊號。
瀏覽器取消請求時,AspNetCore根據CancellationTokenModelBinder自動將HttpContext.RequestAborted這個token繫結到Action的CancellationToken 引數,CancellationTokenModelBinder將會在呼叫AddMvc()或services.AddMvcCore()時被注入。
public async Task<ActionResult> MyReallySlowReport(CancellationToken cancellationToken) { List<ReportItem> items; using (ApplicationDbContext context = new ApplicationDbContext()) { items = await context.ReportItems.ToListAsync(cancellationToken); } return View(items); }
很容易取消SQL的查詢行為,因為上述EF的呼叫api支援取消非同步操作; 對於自定義的長耗時查詢行為,可以使用CancllationToken的原生觸發用法:
public async Task<ActionResult> MyReallySlowReport(CancellationToken cancellationToken) { List<ReportItem> items; using (ApplicationDbContext context = new ApplicationDbContext()) { items = await context.ReportItems.ToListAsync(cancellationToken); } foreach (var item in items) { cancellationToken.ThrowIfCancellationRequested(); // slow non-cancellable work Thread.Sleep(1000); } return View(items); }
P2 處理取消非同步操作向上丟擲的異常
Web伺服器觸發取消訊號,一般會向上會丟擲 OperationCanceledException 或者 TaskCancellationException,所以為了記錄這種非常規異常,建議採用獨立的ExceptionFilter記錄。
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); } } }
P3 想要得到CTO的稱讚,可不是那麼簡單。
以上只是後端程式設計師利用取消機制緩解非同步查詢瓶頸的後端操作,從web應用全流程角度思考,這個優化還能提升嗎?
> 以上是傳統的網頁請求場景,在取消請求時,瀏覽器幫助我們發起了Cancellation訊號。
> 想想日益常見的SPA程式(單頁面程式),絕大部分頁面請求都是Ajax請求,你點選應用的另外一個“頁面(JS程式碼維護頁面導航),瀏覽器不會自動取消請求。
所以在SPA應用中,前端要自己發出取消請求的訊號:
var xhr = $.get("/api/myslowreport", function(data){ //show the data }); //If the user navigates away from this page xhr.abort()
前後端程式猿通力配合, 應用的吞吐量和響應能力極大提升, CTO要給各位加薪了。