一次.net?core異步線程設(shè)置超時(shí)時(shí)間的實(shí)戰(zhàn)記錄
前言:
刷帖子看到一篇 Go 記錄一次groutine通信與context控制 看了一下需求背景,挺有意思的,琢磨了下.net core下的實(shí)現(xiàn)
需求背景:
項(xiàng)目中需要定期執(zhí)行任務(wù)A來(lái)做一些輔助的工作,A的執(zhí)行需要在超時(shí)時(shí)間內(nèi)完成,如果本次執(zhí)行超時(shí)了,那就不對(duì)本次的執(zhí)行結(jié)果進(jìn)行處理(即放棄這次執(zhí)行)。同時(shí)A又依賴(lài)B,C兩個(gè)子任務(wù)的執(zhí)行結(jié)果。B, C之間相互獨(dú)立,可以并行的執(zhí)行。但無(wú)論B,C哪一個(gè)執(zhí)行失敗或超時(shí)都會(huì)導(dǎo)致本次任務(wù)執(zhí)行失敗。
需求提煉:
- A任務(wù)必須在指定時(shí)間內(nèi)完成,否則任務(wù)失敗
- A任務(wù)依賴(lài)B,C任務(wù),B,C可以并行,任何一個(gè)失敗,則A任務(wù)失敗
- A任務(wù)在超時(shí)時(shí)間內(nèi),是否需求記錄子任務(wù)執(zhí)行詳情(根據(jù)業(yè)務(wù)需求來(lái)定)
.net里設(shè)置超時(shí)的 Task
public static class TaskHelper { // 有返回值 public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout) { using (var timeoutCancellationTokenSource = new CancellationTokenSource()) { var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)); if (completedTask == task) { timeoutCancellationTokenSource.Cancel(); return await task; // Very important in order to propagate exceptions } else { throw new TimeoutException("The operation has timed out."); } } } // 無(wú)返回值 public static async Task TimeoutAfter(this Task task, TimeSpan timeout) { using (var timeoutCancellationTokenSource = new CancellationTokenSource()) { var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)); if (completedTask == task) { timeoutCancellationTokenSource.Cancel(); await task; // Very important in order to propagate exceptions } else { throw new TimeoutException("The operation has timed out."); } } } }
這里參考資料,寫(xiě)了個(gè)拓展方法,主要用到CancellationTokenSource 與 Task.WhenAny
可以參考 C#中CancellationToken和CancellationTokenSource用法
可以參考 Task.WhenAny 方法
這里需要特別注意,在異步操作里,如果異步已經(jīng)執(zhí)行了,再執(zhí)行取消時(shí)無(wú)效的,這是就需要我們自己在異步委托中檢測(cè)了
知道了這兩個(gè)函數(shù)的作用,這段代碼就很好理解了,通過(guò)Task.WhenAny返回最先完成的任務(wù),如果是業(yè)務(wù)任務(wù)先完成,則調(diào)用timeoutCancellationTokenSource.Cancel()終止超時(shí)任務(wù),等待業(yè)務(wù)任務(wù)結(jié)果,反之則直接拋出timeout異常
測(cè)試代碼
[TestMethod] public async Task TestMethod1() { //A 任務(wù)必須在指定時(shí)間內(nèi)完成,否則任務(wù)失敗 //A 任務(wù)依賴(lài)B,C任務(wù),B,C可以并行,任何一個(gè)失敗,則A任務(wù)失敗 //A任務(wù) try { //有效時(shí)間3s var timeOut = TimeSpan.FromSeconds(3); await Task.Run(async () => { List<Task<(string, bool)>> tasks = new List<Task<(string, bool)>>(); //B任務(wù) tasks.Add(Task.Run(async () => { return ("B", await TestTask("B")); }).TimeoutAfter(timeOut)); //C任務(wù) tasks.Add(Task.Run(async () => { return ("C", await TestTask("C")); }).TimeoutAfter(timeOut)); var res = await Task.WhenAll(tasks); //兩個(gè)任務(wù),任何一個(gè)失敗,則A任務(wù)失敗 foreach (var item in res) { Console.WriteLine(item); } }).TimeoutAfter(timeOut); } catch (Exception ex) { Console.WriteLine("A任務(wù)執(zhí)行超時(shí)了"); } //await Task.Delay(3000); } public async Task<bool> TestTask(string name) { var startTime = DateTime.Now; Console.WriteLine($"{startTime}---->{name}任務(wù)開(kāi)始執(zhí)行"); //隨機(jī)堵塞1-5s var t = new Random().Next(1, 5); await Task.Delay(t * 1000); var endTime = DateTime.Now; ; var time = (endTime - startTime).TotalSeconds; //隨機(jī)數(shù),模擬業(yè)務(wù)是否成功 var res = new Random().Next(1, 10); Console.WriteLine($"{endTime}---->{name}任務(wù)執(zhí)行完畢,耗時(shí){time} s"); return res <= 7; }
測(cè)試截圖
搞定收工
故事在這里就結(jié)束了嗎? 顯然沒(méi)有,這么簡(jiǎn)單也沒(méi)必要水一篇博客了
我們能做到在3s內(nèi)響應(yīng)結(jié)果,也算基本上滿(mǎn)足了需求,那超時(shí)的子任務(wù),是否會(huì)繼續(xù)執(zhí)行呢?
仔細(xì)看代碼,就算超時(shí),也是停止的Task.Delay() 這個(gè)線程,與業(yè)務(wù)線程沒(méi)有半毛錢(qián)關(guān)系,那業(yè)務(wù)線程肯定會(huì)繼續(xù)執(zhí)行
眼尖的同學(xué)已經(jīng)看到最后一張圖,B任務(wù)執(zhí)行了3.0076088s,按道理B任務(wù)是已經(jīng)超時(shí)了,這段話是不會(huì)輸出的,那如果我讓主線程晚點(diǎn)退出,那超時(shí)的子線程是否能正常執(zhí)行, //await Task.Delay(3000); 將這段代碼取消注釋,再來(lái)觀看結(jié)果
有沒(méi)有一種被欺騙的感覺(jué),寫(xiě)了一個(gè)假的超時(shí)時(shí)間,哈哈哈哈.....
這里需要特別注意,在異步操作里,如果異步已經(jīng)執(zhí)行了,再執(zhí)行取消時(shí)無(wú)效的,這是就需要我們自己在異步委托中檢測(cè)了
如果寫(xiě)了一個(gè)死循環(huán)的task,那后果將不堪設(shè)想,這個(gè)時(shí)候,就需要慎重了,在使用多線程取消令牌的時(shí)候,除了需要執(zhí)行Cancel()方法,還需要在子任務(wù)內(nèi)自己捕獲CancellationTokenSource.Token.ThrowIfCancellationRequested()
總結(jié)
到此這篇關(guān)于一次.net core異步線程設(shè)置超時(shí)時(shí)間的文章就介紹到這了,更多相關(guān).net core異步線程設(shè)置超時(shí)時(shí)間內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
.NET使用Collections.Pooled提升性能優(yōu)化的方法
這篇文章主要介紹了.NET使用Collections.Pooled性能優(yōu)化的方法,今天要給大家分享類(lèi)庫(kù)Collections.Pooled,它是通過(guò)池化內(nèi)存來(lái)達(dá)到降低內(nèi)存占用和GC的目的,另外也會(huì)帶大家看看源碼,為什么它會(huì)帶來(lái)這些性能提升,一起通過(guò)本文學(xué)習(xí)下吧2022-05-05ASP.NET Core MVC如何實(shí)現(xiàn)運(yùn)行時(shí)動(dòng)態(tài)定義Controller類(lèi)型
這篇文章主要介紹了ASP.NET Core MVC如何實(shí)現(xiàn)運(yùn)行時(shí)動(dòng)態(tài)定義Controller類(lèi)型,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06Linux服務(wù)器下利用Docker部署.net Core項(xiàng)目的全過(guò)程
這篇文章主要給大家介紹了關(guān)于在Linux服務(wù)器下利用Docker部署.net Core項(xiàng)目的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用.net Core具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07VS2010/VS2013項(xiàng)目創(chuàng)建 ADO.NET連接mysql/sql server詳細(xì)步驟
這篇文章主要介紹了VS2010/VS2013項(xiàng)目創(chuàng)建,及ADO.NET連接mysql/sql server詳細(xì)步驟,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10asp.net GridView中超鏈接的使用(帶參數(shù))
在GridView中,點(diǎn)擊鏈接列跳轉(zhuǎn)到指定頁(yè)面的實(shí)現(xiàn)代碼,需要的朋友可以參考下。2010-03-03