網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
一、需求
在之前的帖子中,介紹了?async / await 的用法,那么新的問(wèn)題又來(lái)了,如果調(diào)用一個(gè)異步方法后,一直不給返回值結(jié)果怎么辦呢?這就涉及到怎么取消任務(wù)了。添加一個(gè)任務(wù)后,如果固定時(shí)間內(nèi)沒用返回結(jié)果,那么就取消執(zhí)行,并且在多個(gè)任務(wù)同時(shí)執(zhí)行的時(shí)候,依然按順序來(lái)執(zhí)行,這就是本文章要實(shí)現(xiàn)的功能。
二、Task取消任務(wù)
先介紹一下 Task 結(jié)束任務(wù)的傳統(tǒng)用法。在 C# 以前的2.0 等版本中,線程是可以強(qiáng)制終止的,到了后面,就不允許強(qiáng)制去結(jié)束線程的,后面微軟就提供了一個(gè)?CancellationTokenSource 相關(guān)的接口開源取消任務(wù)。于是我搜索了大量的帖子,一看全是各種抄襲,相互抄來(lái)抄去的,搞的我真的火冒三丈,完全是浪費(fèi)時(shí)間,那么總結(jié)取消的方法如下:
namespace 取消任務(wù)3
{
internal class Program
{
static CancellationTokenSource source = new CancellationTokenSource();
static void Main(string[] args)
{
Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(100);
Console.WriteLine("oh my god");
source.Token.ThrowIfCancellationRequested();
}
}, source.Token);
Thread.Sleep(2000);
Console.WriteLine("取消任務(wù)");
source.Cancel();
Console.ReadKey();
}
}
}
運(yùn)行:
這個(gè)可以取消任務(wù)是吧,那下面換一個(gè)寫法:
namespace 取消任務(wù)3
{
internal class Program
{
static CancellationTokenSource source = new CancellationTokenSource();
static void Main(string[] args)
{
Task.Run(() =>
{
Thread.Sleep(3000);
Console.WriteLine("oh my god");
source.Token.ThrowIfCancellationRequested();
}, source.Token);
Thread.Sleep(1000);
Console.WriteLine("取消任務(wù)");
source.Cancel();
Console.ReadKey();
}
}
}
任務(wù)中等待三秒,我在等待一秒后取消任務(wù),看看結(jié)果:
這回就不管用了,任務(wù)明明取消了,但結(jié)果依然執(zhí)行了。
根據(jù)他們寫的例子,可以總結(jié)一點(diǎn),就是要想取消任務(wù),在任務(wù)中,必須加入 for 或者 while 循環(huán),并且在下一輪循環(huán)中,執(zhí)行到 source.Token.ThrowIfCancellationRequested() 這句才能取消任務(wù)。
換個(gè)寫法,如果非得用 for 或者 while 循環(huán)這樣的語(yǔ)法才能取消任務(wù),我在 while 循環(huán)中加入一個(gè)判斷,如果等于 true,直接跳出循環(huán),這不也是中斷了任務(wù),所以說(shuō)?CancellationTokenSource 真的意義不大
namespace 取消任務(wù)4
{
internal class Program
{
static void Main(string[] args)
{
bool isOut = false;
var task1 = Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
if (isOut) return;
Console.WriteLine("執(zhí)行中" + i);
Thread.Sleep(500);
}
});
Thread.Sleep(2000);
Console.WriteLine("取消任務(wù)");
isOut= true;
Console.ReadKey();
}
}
}
運(yùn)行:
三、Task取消任務(wù)的回調(diào)
取消任務(wù)也是可以加入回調(diào)的,如下:
namespace 取消任務(wù)2
{
internal class Program
{
static CancellationTokenSource source = new CancellationTokenSource();
static void Main(string[] args)
{
var task1 = Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
source.Token.ThrowIfCancellationRequested();
Console.WriteLine("執(zhí)行中" + i);
Thread.Sleep(500);
}
}, source.Token);
//在指定的毫秒數(shù)后取消task執(zhí)行
source.CancelAfter(2 * 1000);
//取消任務(wù)后的回調(diào)
source.Token.Register(() =>
{
//不延遲會(huì)獲取不到正確的狀態(tài)
Thread.Sleep(50);
Console.WriteLine("task1狀態(tài):" + task1.Status);
Console.WriteLine("IsFaulted狀態(tài):" + task1.IsFaulted);//由于未處理的異常,任務(wù)已完成。
Console.WriteLine("IsCompleted狀態(tài):" + task1.IsCompleted);//獲取一個(gè)值,該值指示任務(wù)是否已完成。
});
Console.ReadKey();
}
}
}
?運(yùn)行:
四、Task超時(shí)處理的實(shí)現(xiàn)
在上面的介紹中可以看到,Task取消任務(wù)傳統(tǒng)的用法并不好用,必須在里面加上條件判斷,如果滿足條件就跳出?for 或者 while 循環(huán),達(dá)到方法執(zhí)行完成的目的,而并不是真的終止了任務(wù)。
那么這么需求要如何去完成呢,微軟官方也提供了一個(gè)叫 Task.WhenAny 接口,可以實(shí)現(xiàn)這個(gè)功能,下面就看看如何實(shí)現(xiàn)的。
新建一個(gè)基于 .Net6 的 Winform 項(xiàng)目,新建一個(gè)腳本?Lib.cs
namespace Utils
{
public static class Lib
{
public static async Task<bool> TryWithTimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout, Action<TResult>? successor = null)
{
//用于取消任務(wù)
using CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource();
//WhenAny 等待所有任務(wù)結(jié)束,這里加入了超時(shí)時(shí)間
//ConfigureAwait 配置用來(lái)等待 任務(wù)1的警報(bào),返回值可以獲取到改任務(wù)
Task? completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)).ConfigureAwait(continueOnCapturedContext: false);
//如果當(dāng)前任務(wù)完成了,并且匹配
if (completedTask == task)
{
//取消任務(wù)
timeoutCancellationTokenSource.Cancel();
//得到任務(wù)返回結(jié)果
var result = await task.ConfigureAwait(continueOnCapturedContext: false);
//執(zhí)行回調(diào)
if(successor != null)
successor(result);
return true;
}
else //任務(wù)超時(shí)
return false;
}
}
}
界面如下,就幾個(gè)按鈕
代碼:
using Utils;
namespace 異步編程
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Test1();
}
private void button2_Click(object sender, EventArgs e)
{
Test2();
}
private void Button_ClearConsole_Click(object sender, EventArgs e)
{
Console.Clear();
}
private async void Test1()
{
var task1 = Task.Run(() =>
{
Thread.Sleep(3000);
return "task1";
});
bool res1 = await task1.TryWithTimeoutAfter(TimeSpan.FromSeconds(4), (string msg) =>
{
Console.WriteLine("-----------------------task1回調(diào):" + msg);
});
string isTimeout1 = res1 == true ? "沒超時(shí)" : "超時(shí)";
Console.WriteLine("任務(wù)1:" + isTimeout1);
var task2 = Task.Run(() =>
{
Thread.Sleep(3000);
return "task2";
});
bool res2 = await task2.TryWithTimeoutAfter(TimeSpan.FromSeconds(4), (string msg) =>
{
Console.WriteLine("-----------------------task2回調(diào):" + msg);
});
string isTimeout2 = res2 == true ? "沒超時(shí)" : "超時(shí)";
Console.WriteLine("任務(wù)2:" + isTimeout2);
}
private async void Test2()
{
var task3 = Task.Run(() =>
{
Thread.Sleep(3000);
return "task3";
});
bool res3 = await task3.TryWithTimeoutAfter(TimeSpan.FromSeconds(4), (string msg) =>
{
Console.WriteLine("-----------------------task3回調(diào):" + msg);
});
string isTimeou3 = res3 == true ? "沒超時(shí)" : "超時(shí)";
Console.WriteLine("任務(wù)2:" + isTimeou3);
}
}
}
按鈕1和按鈕2 方法里有三個(gè)異步方法,超時(shí)時(shí)間都是4秒,也就是說(shuō),如果方法在4秒之內(nèi)沒有返回值則為失敗。
分別點(diǎn)擊按鈕1,按鈕2
在按鈕1方法里有兩個(gè)異步方法,異步方法1執(zhí)行完成后,才能執(zhí)行異步方法2,所以異步方法2要比異步方法3更慢一些。
下面就將三個(gè)異步方法的超時(shí)時(shí)間改為1秒,看看效果:
返回超時(shí),而且任務(wù)也沒有執(zhí)行,這樣就實(shí)現(xiàn)了我們的想要的效果了。?
五、Task.WhenAny 的異常
在一系列的測(cè)試中,我發(fā)現(xiàn)了 Task.WhenAny 這個(gè)接口在 .Net6 的控制臺(tái)項(xiàng)目的異常之處,執(zhí)行一次是正常的,如果在一個(gè)方法內(nèi)同時(shí)執(zhí)行多次,返回結(jié)果就開始亂了,在 Winform 項(xiàng)目中是沒有這種事的,下面開始演示。
新建一個(gè)基于 .Net6 的控制臺(tái)項(xiàng)目,?將上面的 Lib.cs 代碼復(fù)制到項(xiàng)目中來(lái)。
代碼:
using Utils;
namespace 異步編程2
{
internal class Program
{
static void Main(string[] args)
{
AwaitReturnValue();
Console.ReadKey();
}
public static async void AwaitReturnValue()
{
var task1 = Task.Run(() =>
{
Thread.Sleep(3000);
return "task1";
});
for (int i = 0; i < 10; i++)
{
bool res1 = await task1.TryWithTimeoutAfter(TimeSpan.FromSeconds(1), (string msg) =>
{
Console.WriteLine("task1回調(diào):" + msg);
});
string isTimeout = res1 == true ? "沒超時(shí)" : "超時(shí)";
Console.WriteLine(string.Format("結(jié)果{0}:{1}", i, isTimeout));
}
}
}
}
運(yùn)行:
任務(wù)前兩次是正確的,后面返回的基本全是錯(cuò)誤的,原因我估計(jì)是 Task 任務(wù)內(nèi)部等待時(shí)間是3秒,調(diào)用了前兩次時(shí)間沒有超過(guò)三秒,所以返回是正確的,后面超過(guò)3秒后,全當(dāng)在超時(shí)范圍內(nèi)返回了。
六、其他的寫法
超時(shí)取消任務(wù)的寫法可以有多種,其實(shí)萬(wàn)變不離其宗,都是用 Task.WhenAny 方法實(shí)現(xiàn)的,代碼我全部放一個(gè)類里面了,有興趣的可以看看,有很多的高級(jí)語(yǔ)法,確實(shí)是值得學(xué)習(xí)的。
代碼:
namespace 異步編程1
{
public static class Lib1
{
public static async Task<TResult?> TimeoutAfter<TResult>(this Task<TResult> task, int timeout)
{
using (var cancelToken = new CancellationTokenSource())
{
Task completedTask = await Task.WhenAny(task, Task.Delay(timeout, cancelToken.Token));
if (completedTask == task)
{
cancelToken.Cancel();
return await task;
}
else
{
// 超時(shí)處理
Console.WriteLine("超時(shí)了");
return default;
}
}
}
public static async Task<bool> OnTimeout<T>(T t, Action<T> action, int waitms) where T : Task
{
if (!(await Task.WhenAny(t, Task.Delay(waitms)) == t))
{
action(t);
return true;
}
else
{
return false;
}
}
public static async Task<bool> TryWithTimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout, Action<TResult> successor)
{
using var timeoutCancellationTokenSource = new CancellationTokenSource();
var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)).ConfigureAwait(continueOnCapturedContext: false);
if (completedTask == task)
{
timeoutCancellationTokenSource.Cancel();
// propagate exception rather than AggregateException, if calling task.Result.
var result = await task.ConfigureAwait(continueOnCapturedContext: false);
successor(result);
return true;
}
else
return false;
}
public static async Task<bool> BeforeTimeout(Task task, int millisecondsTimeout)
{
if (task.IsCompleted) return true;
if (millisecondsTimeout == 0) return false;
if (millisecondsTimeout == Timeout.Infinite)
{
await Task.WhenAll(task);
return true;
}
var tcs = new TaskCompletionSource<object>();
using (var timer = new Timer(state => ((TaskCompletionSource<object>)state).TrySetCanceled(), tcs, millisecondsTimeout, Timeout.Infinite))
{
return await Task.WhenAny(task, tcs.Task) == task;
}
}
}
}
原文鏈接:https://blog.csdn.net/qq_38693757/article/details/127935673
相關(guān)推薦
- 2023-12-11 Spring依賴注入DI
- 2022-06-18 Android?ScrollView實(shí)現(xiàn)滾動(dòng)超過(guò)邊界松手回彈_Android
- 2023-10-27 解決webpack打包后圖片加載失敗的bug(適用于所有本地靜態(tài)資源)
- 2022-01-31 為什么要使用3×3卷積?& 1*1卷積的作用是什么?& 對(duì)ResNet結(jié)構(gòu)的一些理解
- 2023-07-10 Spring MVC 詳解(連接、獲取參數(shù)、返回?cái)?shù)據(jù))
- 2022-11-28 ContentProvider客戶端處理provider邏輯分析_Android
- 2022-12-06 Python?list?append方法之給列表追加元素_python
- 2022-03-26 oracle自動(dòng)統(tǒng)計(jì)信息時(shí)間的修改過(guò)程記錄_oracle
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支