.NET Core 取消令牌:CancellationToken

在 .NET 開(kāi)發(fā)中屎开,CancellationToken(取消令牌)是一項(xiàng)比較重要的功能告希,掌握并合理的使用 CancellationToken 可以提升服務(wù)的性能。特別在異步編程中酸些,我常常會(huì)以創(chuàng)建 Task 的方式利用多線程執(zhí)行一些耗時(shí)或非核心業(yè)務(wù)邏輯荞驴,表面上看既提高了整個(gè)流程的執(zhí)行速度,又充分利用了服務(wù)器資源进苍。然而類(lèi)似 Task 的方式如果沒(méi)設(shè)置過(guò)取消令牌加缘,一旦開(kāi)啟,是無(wú)法被外部取消的觉啊,所以當(dāng)主線程出異臣鸷辏或被提前終止時(shí),已開(kāi)啟的異步線程其實(shí)依然在執(zhí)行杠人,這時(shí)對(duì)服務(wù)器資源可能是一種浪費(fèi)勋乾,而 CancellationToken 就可以對(duì)這類(lèi)情況進(jìn)行一定的補(bǔ)救。

下面通過(guò)幾種常見(jiàn)的使用場(chǎng)景來(lái)介紹 CancellationToken嗡善。

在 HttpClient 中的使用

HttpClient 是開(kāi)發(fā)中比較常用的一個(gè)組件辑莫,關(guān)于超時(shí)可通過(guò) Timeout 參數(shù)進(jìn)行設(shè)置,其實(shí)它也是可以通過(guò)配置 CancellationToken 來(lái)實(shí)現(xiàn)超時(shí)定義罩引,使用 CancellationToken 的最大好處是調(diào)用鏈共享此令牌狀態(tài)各吨,狀態(tài)變更時(shí)會(huì)自動(dòng)做出響應(yīng)。

public async Task<string> GetHomeAsync(CancellationToken cancellationToken = default)
{
  var client = _httpClientFactory.CreateClient();
  var response = await client.GetAsync("https://github.com/", cancellationToken);
  response.EnsureSuccessStatusCode();
  return await response.Content.ReadAsStringAsync();
}
public async Task<string> GetGithubHome()
{
  var cts = new CancellationTokenSource(1000);
  var result = await _githubService.GetHomeAsync(cts.Token);
  return result;
}

Github 一般訪問(wèn)會(huì)比較慢袁铐,可通過(guò)設(shè)置 1s 演示效果:

在 gRPC 中的使用

通過(guò) VS 的 gRPC 服務(wù)模板創(chuàng)建一個(gè) gRPC 服務(wù)端(如果對(duì) gRPC 的使用還不太了解揭蜒,參考官方文檔 玩起來(lái)吧),服務(wù)端主要提供一個(gè)獲取用戶列表 (GetList) 的接口昭躺。實(shí)現(xiàn)如下忌锯,_userRepository 內(nèi)部是基于 MongoDB 實(shí)現(xiàn)的查詢用戶數(shù)據(jù),對(duì)應(yīng)使用的 MongoDB.Driver 提供的方法默認(rèn)已支持設(shè)置 CancellationToken领炫,所以這里直接引用 ServerCallContext 上下文中的 CancellationToken偶垮,而此 CancellationToken 又是從客戶端傳遞來(lái)的,所以 CancellationToken 將作用于整個(gè)調(diào)用鏈中帝洪。另外如果在客戶端動(dòng)態(tài)取消了此令牌似舵,服務(wù)器也將會(huì)收到通知。

public override async Task<GetListReply> GetList(GetListRequest request, ServerCallContext context)
{
  await Task.Delay(1000); // 模擬效果葱峡,服務(wù)端停1s
  var users = await _userRepository.GetListAsync(context.CancellationToken);
  var reply = new GetListReply();
  foreach (var item in users)
  {
    reply.Users.Add(new UserModel { UserId = item.UserId, Name = item.Name });
  }
  return reply;
}

Client 端主要代碼如下砚哗,在接口層創(chuàng)建了 CancellationTokenSource 對(duì)象,并設(shè)置了令牌的過(guò)期時(shí)間砰奕,即在發(fā)起遠(yuǎn)程調(diào)用時(shí)蛛芥,如果 1s 內(nèi)沒(méi)返回提鸟,那就取消這個(gè)調(diào)用。

public class UserService : IUserService
{
  private readonly UserClient _client;

  public UserService()
  {
    var channel = GrpcChannel.ForAddress("https://localhost:5001");
    _client = new UserClient(channel);
  }

  public async Task<GetListReply> GetListAsync(CancellationToken cancellationToken = default)
  {
    return await _client.GetListAsync(new GetListRequest(), cancellationToken: cts.Token);
  }
}
[HttpGet]
public async Task<string> GetUserList()
{
  var cts = new CancellationTokenSource(1000);
  var result = await _userService.GetListAsync(cts.Token);
  return JsonConvert.SerializeObject(result.Users);
}

在 WebAPI 中的使用

前端調(diào)用后端的接口一般是基于 Ajax 來(lái)實(shí)現(xiàn)仅淑,當(dāng)瀏覽器網(wǎng)頁(yè)被 連續(xù) F5 刷新頁(yè)面加載中被停止Ajax 請(qǐng)求被主動(dòng) abort 時(shí)称勋,控制臺(tái) network 看板中會(huì)出現(xiàn)一些狀態(tài)為 canceled 的請(qǐng)求,如下:

對(duì)于這類(lèi)請(qǐng)求涯竟,客戶端雖然主動(dòng)放棄了赡鲜,如果服務(wù)端沒(méi)有相應(yīng)處理,其實(shí)接口對(duì)應(yīng)的后端程序還是在不停的執(zhí)行庐船,只是這個(gè)執(zhí)行結(jié)果不會(huì)被使用而已银酬,所以這其實(shí)是非常浪費(fèi)服務(wù)器資源的。

實(shí)際上瀏覽器取消請(qǐng)求時(shí)筐钟,服務(wù)端會(huì)將 HttpContext.RequestAborted 中的 Token 綁定到 Action 的 CancellationToken 參數(shù)揩瞪。我們只需在接口中增加參數(shù) CancellationToken,并將其傳入其他接口調(diào)用中盗棵,程序識(shí)別到令牌被取消就會(huì)自動(dòng)放棄繼續(xù)執(zhí)行壮韭。

[HttpGet]
public async Task<string> Index(CancellationToken cancellationToken)
{
  try
  {
    await _userService.GetListAsync(cancellationToken);
    await Task.Delay(5000); // 等待期間,取消請(qǐng)求(Postman 即可模擬)
    await _githubService.GetHomeAsync(cancellationToken);
  }
  catch (Exception ex)
  {
    Console.WriteLine(ex.Message + Environment.NewLine + ex.StackTrace);
  }

  return "ok";
}

對(duì)于 WebAPI 接口被取消調(diào)用的場(chǎng)景纹因,特別是對(duì)于查詢功能的接口喷屋,CancellationToken 的傳遞就顯得尤為必要了,它能減少很多底層服務(wù)接口的無(wú)效調(diào)用瞭恰。

最后針對(duì)取消令牌產(chǎn)生的異常需要收尾干凈屯曹,一般像 WebAPI 項(xiàng)目可以使用自帶的過(guò)濾器或具有 AOP 功能的組件,gRPC 服務(wù)可使用自帶的攔截器功能惊畏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恶耽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子颜启,更是在濱河造成了極大的恐慌偷俭,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缰盏,死亡現(xiàn)場(chǎng)離奇詭異涌萤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)口猜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)负溪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人济炎,你說(shuō)我怎么就攤上這事川抡。” “怎么了须尚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵崖堤,是天一觀的道長(zhǎng)侍咱。 經(jīng)常有香客問(wèn)我,道長(zhǎng)密幔,這世上最難降的妖魔是什么放坏? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮老玛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钧敞。我一直安慰自己蜡豹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布溉苛。 她就那樣靜靜地躺著镜廉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪愚战。 梳的紋絲不亂的頭發(fā)上娇唯,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音寂玲,去河邊找鬼塔插。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拓哟,可吹牛的內(nèi)容都是我干的想许。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼断序,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼流纹!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起违诗,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤漱凝,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后诸迟,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體茸炒,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年亮蒋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扣典。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡慎玖,死狀恐怖贮尖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情趁怔,我是刑警寧澤湿硝,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布薪前,位于F島的核電站,受9級(jí)特大地震影響关斜,放射性物質(zhì)發(fā)生泄漏示括。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一痢畜、第九天 我趴在偏房一處隱蔽的房頂上張望垛膝。 院中可真熱鬧,春花似錦丁稀、人聲如沸吼拥。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)凿可。三九已至,卻和暖如春授账,著一層夾襖步出監(jiān)牢的瞬間枯跑,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工白热, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留敛助,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓棘捣,卻偏偏與公主長(zhǎng)得像辜腺,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子乍恐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354