C#多線程系列之任務(wù)基礎(chǔ)(三)
TaskAwaiter
先說(shuō)一下 TaskAwaiter
,TaskAwaiter
表示等待異步任務(wù)完成的對(duì)象并為結(jié)果提供參數(shù)。
Task 有個(gè) GetAwaiter()
方法,會(huì)返回TaskAwaiter
或TaskAwaiter<TResult>
,TaskAwaiter
類(lèi)型在 System.Runtime.CompilerServices
命名空間中定義。
TaskAwaiter
類(lèi)型的屬性和方法如下:
屬性:
屬性 | 說(shuō)明 |
---|---|
IsCompleted | 獲取一個(gè)值,該值指示異步任務(wù)是否已完成。 |
方法:
方法 | 說(shuō)明 |
---|---|
GetResult() | 結(jié)束異步任務(wù)完成的等待。 |
OnCompleted(Action) | 將操作設(shè)置為當(dāng) TaskAwaiter 對(duì)象停止等待異步任務(wù)完成時(shí)執(zhí)行。 |
UnsafeOnCompleted(Action) | 計(jì)劃與此 awaiter 相關(guān)異步任務(wù)的延續(xù)操作。 |
使用示例如下:
static void Main() { Task<int> task = new Task<int>(()=> { Console.WriteLine("我是前驅(qū)任務(wù)"); Thread.Sleep(TimeSpan.FromSeconds(1)); return 666; }); TaskAwaiter<int> awaiter = task.GetAwaiter(); awaiter.OnCompleted(()=> { Console.WriteLine("前驅(qū)任務(wù)完成時(shí),我就會(huì)繼續(xù)執(zhí)行"); }); task.Start(); Console.ReadKey(); }
另外,我們前面提到過(guò),任務(wù)發(fā)生未經(jīng)處理的異常,任務(wù)被終止,也算完成任務(wù)。
延續(xù)的另一種方法
上一節(jié)我們介紹了 .ContinueWith()
方法來(lái)實(shí)現(xiàn)延續(xù),這里我們介紹另一個(gè)延續(xù)方法 .ConfigureAwait()
。
.ConfigureAwait()
如果要嘗試將延續(xù)任務(wù)封送回原始上下文,則為 true
;否則為 false
。
我來(lái)解釋一下, .ContinueWith()
延續(xù)的任務(wù),當(dāng)前驅(qū)任務(wù)完成后,延續(xù)任務(wù)會(huì)繼續(xù)在此線程上繼續(xù)執(zhí)行。這種方式是同步的,前者和后者連續(xù)在一個(gè)線程上運(yùn)行。
.ConfigureAwait(false)
方法可以實(shí)現(xiàn)異步,前驅(qū)方法完成后,可以不理會(huì)后續(xù)任務(wù),而且后續(xù)任務(wù)可以在任意一個(gè)線程上運(yùn)行。這個(gè)特性在 UI 界面程序上特別有用。
其使用方法如下:
static void Main() { Task<int> task = new Task<int>(()=> { Console.WriteLine("我是前驅(qū)任務(wù)"); Thread.Sleep(TimeSpan.FromSeconds(1)); return 666; }); ConfiguredTaskAwaitable<int>.ConfiguredTaskAwaiter awaiter = task.ConfigureAwait(false).GetAwaiter(); awaiter.OnCompleted(()=> { Console.WriteLine("前驅(qū)任務(wù)完成時(shí),我就會(huì)繼續(xù)執(zhí)行"); }); task.Start(); Console.ReadKey(); }
ConfiguredTaskAwaitable<int>.ConfiguredTaskAwaiter
擁有跟 TaskAwaiter
一樣的屬性和方法。
.ContinueWith()
跟 .ConfigureAwait(false)
還有一個(gè)區(qū)別就是 前者可以延續(xù)多個(gè)任務(wù)和延續(xù)任務(wù)的任務(wù)(多層)。后者只能延續(xù)一層任務(wù)(一層可以有多個(gè)任務(wù))。
另一種創(chuàng)建任務(wù)的方法
前面提到提到過(guò),創(chuàng)建任務(wù)的三種方法:new Task()
、Task.Run()
、Task.Factory.SatrtNew()
,現(xiàn)在來(lái)學(xué)習(xí)第四種方法:TaskCompletionSource<TResult>
類(lèi)型。
我們來(lái)看看 TaskCompletionSource<TResulr>
類(lèi)型的屬性和方法:
屬性:
屬性 | 說(shuō)明 |
---|---|
Task | 獲取由此 Task 創(chuàng)建的 TaskCompletionSource。 |
方法:
方法 | 說(shuō)明 |
---|---|
SetCanceled() | 將基礎(chǔ) Task 轉(zhuǎn)換為 Canceled 狀態(tài)。 |
SetException(Exception) | 將基礎(chǔ) Task 轉(zhuǎn)換為 Faulted 狀態(tài),并將其綁定到一個(gè)指定異常上。 |
SetException(IEnumerable) | 將基礎(chǔ) Task 轉(zhuǎn)換為 Faulted 狀態(tài),并對(duì)其綁定一些異常對(duì)象。 |
SetResult(TResult) | 將基礎(chǔ) Task 轉(zhuǎn)換為 RanToCompletion 狀態(tài)。 |
TrySetCanceled() | 嘗試將基礎(chǔ) Task 轉(zhuǎn)換為 Canceled 狀態(tài)。 |
TrySetCanceled(CancellationToken) | 嘗試將基礎(chǔ) Task 轉(zhuǎn)換為 Canceled 狀態(tài)并啟用要存儲(chǔ)在取消的任務(wù)中的取消標(biāo)記。 |
TrySetException(Exception) | 嘗試將基礎(chǔ) Task 轉(zhuǎn)換為 Faulted 狀態(tài),并將其綁定到一個(gè)指定異常上。 |
TrySetException(IEnumerable) | 嘗試將基礎(chǔ) Task 轉(zhuǎn)換為 Faulted 狀態(tài),并對(duì)其綁定一些異常對(duì)象。 |
TrySetResult(TResult) | 嘗試將基礎(chǔ) Task 轉(zhuǎn)換為 RanToCompletion 狀態(tài)。 |
TaskCompletionSource<TResulr>
類(lèi)可以對(duì)任務(wù)的生命周期做控制。
首先要通過(guò) .Task
屬性,獲得一個(gè) Task
或 Task<TResult>
。
TaskCompletionSource<int> task = new TaskCompletionSource<int>(); Task<int> myTask = task.Task; // Task myTask = task.Task;
然后通過(guò) task.xxx()
方法來(lái)控制 myTask
的生命周期,但是呢,myTask 本身是沒(méi)有任務(wù)內(nèi)容的。
使用示例如下:
static void Main() { TaskCompletionSource<int> task = new TaskCompletionSource<int>(); Task<int> myTask = task.Task; // task 控制 myTask // 新開(kāi)一個(gè)任務(wù)做實(shí)驗(yàn) Task mainTask = new Task(() => { Console.WriteLine("我可以控制 myTask 任務(wù)"); Console.WriteLine("按下任意鍵,我讓 myTask 任務(wù)立即完成"); Console.ReadKey(); task.SetResult(666); }); mainTask.Start(); Console.WriteLine("開(kāi)始等待 myTask 返回結(jié)果"); Console.WriteLine(myTask.Result); Console.WriteLine("結(jié)束"); Console.ReadKey(); }
其它例如 SetException(Exception)
等方法,可以自行探索,這里就不再贅述。
參考資料:https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
這篇文章講得不錯(cuò),而且有圖:https://gigi.nullneuron.net/gigilabs/taskcompletionsource-by-example/
實(shí)現(xiàn)一個(gè)支持同步和異步任務(wù)的類(lèi)型
這部分內(nèi)容對(duì) TaskCompletionSource<TResult>
繼續(xù)進(jìn)行講解。
這里我們來(lái)設(shè)計(jì)一個(gè)類(lèi)似 Task 類(lèi)型的類(lèi),支持同步和異步任務(wù)。
- 用戶(hù)可以使用
GetResult()
同步獲取結(jié)果; - 用戶(hù)可以使用
RunAsync()
執(zhí)行任務(wù),使用.Result
屬性異步獲取結(jié)果;
其實(shí)現(xiàn)如下:
/// <summary> /// 實(shí)現(xiàn)同步任務(wù)和異步任務(wù)的類(lèi)型 /// </summary> /// <typeparam name="TResult"></typeparam> public class MyTaskClass<TResult> { private readonly TaskCompletionSource<TResult> source = new TaskCompletionSource<TResult>(); private Task<TResult> task; // 保存用戶(hù)需要執(zhí)行的任務(wù) private Func<TResult> _func; // 是否已經(jīng)執(zhí)行完成,同步或異步執(zhí)行都行 private bool isCompleted = false; // 任務(wù)執(zhí)行結(jié)果 private TResult _result; /// <summary> /// 獲取執(zhí)行結(jié)果 /// </summary> public TResult Result { get { if (isCompleted) return _result; else return task.Result; } } public MyTaskClass(Func<TResult> func) { _func = func; task = source.Task; } /// <summary> /// 同步方法獲取結(jié)果 /// </summary> /// <returns></returns> public TResult GetResult() { _result = _func.Invoke(); isCompleted = true; return _result; } /// <summary> /// 異步執(zhí)行任務(wù) /// </summary> public void RunAsync() { Task.Factory.StartNew(() => { source.SetResult(_func.Invoke()); isCompleted = true; }); } }
我們?cè)?Main 方法中,創(chuàng)建任務(wù)示例:
class Program { static void Main() { // 實(shí)例化任務(wù)類(lèi) MyTaskClass<string> myTask1 = new MyTaskClass<string>(() => { Thread.Sleep(TimeSpan.FromSeconds(1)); return "www.whuanle.cn"; }); // 直接同步獲取結(jié)果 Console.WriteLine(myTask1.GetResult()); // 實(shí)例化任務(wù)類(lèi) MyTaskClass<string> myTask2 = new MyTaskClass<string>(() => { Thread.Sleep(TimeSpan.FromSeconds(1)); return "www.whuanle.cn"; }); // 異步獲取結(jié)果 myTask2.RunAsync(); Console.WriteLine(myTask2.Result); Console.ReadKey(); } }
Task.FromCanceled()
微軟文檔解釋?zhuān)簞?chuàng)建 Task,它因指定的取消標(biāo)記進(jìn)行的取消操作而完成。
這里筆者抄來(lái)了一個(gè)示例:
var token = new CancellationToken(true); Task task = Task.FromCanceled(token); Task<int> genericTask = Task.FromCanceled<int>(token);
網(wǎng)上很多這樣的示例,但是,這個(gè)東西到底用來(lái)干嘛的?new 就行了?
帶著疑問(wèn)我們來(lái)探究一下,來(lái)個(gè)示例:
public static Task Test() { CancellationTokenSource source = new CancellationTokenSource(); source.Cancel(); return Task.FromCanceled<object>(source.Token); } static void Main() { var t = Test(); // 在此設(shè)置斷點(diǎn),監(jiān)控變量 Console.WriteLine(t.IsCanceled); }
Task.FromCanceled()
可以構(gòu)造一個(gè)被取消的任務(wù)。我找了很久,沒(méi)有找到很好的示例,如果一個(gè)任務(wù)在開(kāi)始前就被取消,那么使用 Task.FromCanceled()
是很不錯(cuò)的。
如何在內(nèi)部取消任務(wù)
之前我們討論過(guò),使用 CancellationToken
取消令牌傳遞參數(shù),使任務(wù)取消。但是都是從外部傳遞的,這里來(lái)實(shí)現(xiàn)無(wú)需 CancellationToken
就能取消任務(wù)。
我們可以使用 CancellationToken
的 ThrowIfCancellationRequested()
方法拋出 System.OperationCanceledException
異常,然后終止任務(wù),任務(wù)會(huì)變成取消狀態(tài),不過(guò)任務(wù)需要先傳入一個(gè)令牌。
這里筆者來(lái)設(shè)計(jì)一個(gè)難一點(diǎn)的東西,一個(gè)可以按順序執(zhí)行多個(gè)任務(wù)的類(lèi)。
示例如下:
/// <summary> /// 能夠完成多個(gè)任務(wù)的異步類(lèi)型 /// </summary> public class MyTaskClass { private List<Action> _actions = new List<Action>(); private CancellationTokenSource _source = new CancellationTokenSource(); private CancellationTokenSource _sourceBak = new CancellationTokenSource(); private Task _task; /// <summary> /// 添加一個(gè)任務(wù) /// </summary> /// <param name="action"></param> public void AddTask(Action action) { _actions.Add(action); } /// <summary> /// 開(kāi)始執(zhí)行任務(wù) /// </summary> /// <returns></returns> public Task StartAsync() { // _ = new Task() 對(duì)本示例無(wú)效 _task = Task.Factory.StartNew(() => { for (int i = 0; i < _actions.Count; i++) { int tmp = i; Console.WriteLine($"第 {tmp} 個(gè)任務(wù)"); if (_source.Token.IsCancellationRequested) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("任務(wù)已經(jīng)被取消"); Console.ForegroundColor = ConsoleColor.White; _sourceBak.Cancel(); _sourceBak.Token.ThrowIfCancellationRequested(); } _actions[tmp].Invoke(); } },_sourceBak.Token); return _task; } /// <summary> /// 取消任務(wù) /// </summary> /// <returns></returns> public Task Cancel() { _source.Cancel(); // 這里可以省去 _task = Task.FromCanceled<object>(_source.Token); return _task; } }
Main 方法中:
static void Main() { // 實(shí)例化任務(wù)類(lèi) MyTaskClass myTask = new MyTaskClass(); for (int i = 0; i < 10; i++) { int tmp = i; myTask.AddTask(() => { Console.WriteLine(" 任務(wù) 1 Start"); Thread.Sleep(TimeSpan.FromSeconds(1)); Console.WriteLine(" 任務(wù) 1 End"); Thread.Sleep(TimeSpan.FromSeconds(1)); }); } // 相當(dāng)于 Task.WhenAll() Task task = myTask.StartAsync(); Thread.Sleep(TimeSpan.FromSeconds(1)); Console.WriteLine($"任務(wù)是否被取消:{task.IsCanceled}"); // 取消任務(wù) Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("按下任意鍵可以取消任務(wù)"); Console.ForegroundColor = ConsoleColor.White; Console.ReadKey(); var t = myTask.Cancel(); // 取消任務(wù) Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine($"任務(wù)是否被取消:【{task.IsCanceled}】"); Console.ReadKey(); }
你可以在任一階段取消任務(wù)。
Yield 關(guān)鍵字
迭代器關(guān)鍵字,使得數(shù)據(jù)不需要一次性返回,可以在需要的時(shí)候一條條迭代,這個(gè)也相當(dāng)于異步。
迭代器方法運(yùn)行到 yield return
語(yǔ)句時(shí),會(huì)返回一個(gè) expression
,并保留當(dāng)前在代碼中的位置。 下次調(diào)用迭代器函數(shù)時(shí),將從該位置重新開(kāi)始執(zhí)行。
可以使用 yield break
語(yǔ)句來(lái)終止迭代。
官方文檔:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/yield
網(wǎng)上的示例大多數(shù)都是 foreach
的,有些同學(xué)不理解這個(gè)到底是啥意思。筆者這里簡(jiǎn)單說(shuō)明一下。
我們也可以這樣寫(xiě)一個(gè)示例:
這里已經(jīng)沒(méi)有 foreach
了。
private static int[] list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; private static IEnumerable<int> ForAsync() { int i = 0; while (i < list.Length) { i++; yield return list[i]; } }
但是,同學(xué)又問(wèn),這個(gè) return 返回的對(duì)象 要實(shí)現(xiàn)這個(gè) IEnumerable<T>
才行嘛?那些文檔說(shuō)到什么迭代器接口什么的,又是什么東西呢?
我們可以先來(lái)改一下示例:
private static int[] list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; private static IEnumerable<int> ForAsync() { int i = 0; while (i < list.Length) { int num = list[i]; i++; yield return num; } }
你在 Main 方法中調(diào)用,看看是不是正常運(yùn)行?
static void Main() { foreach (var item in ForAsync()) { Console.WriteLine(item); } Console.ReadKey(); }
這樣說(shuō)明了,yield return
返回的對(duì)象,并不需要實(shí)現(xiàn) IEnumerable<int>
方法。
其實(shí) yield
是語(yǔ)法糖關(guān)鍵字,你只要在循環(huán)中調(diào)用它就行了。
static void Main() { foreach (var item in ForAsync()) { Console.WriteLine(item); } Console.ReadKey(); } private static IEnumerable<int> ForAsync() { int i = 0; while (i < 100) { i++; yield return i; } } }
它會(huì)自動(dòng)生成 IEnumerable<T>
,而不需要你先實(shí)現(xiàn) IEnumerable<T>
。
補(bǔ)充知識(shí)點(diǎn)
線程同步有多種方法:臨界區(qū)(Critical Section)、互斥量(Mutex)、信號(hào)量(Semaphores)、事件(Event)、任務(wù)(Task);
Task.Run()
和Task.Factory.StartNew()
封裝了 Task;Task.Run()
是Task.Factory.StartNew()
的簡(jiǎn)化形式;有些地方
net Task()
是無(wú)效的;但是Task.Run()
和Task.Factory.StartNew()
可以;
到此這篇關(guān)于C#多線程系列之任務(wù)基礎(chǔ)(三)的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
unity實(shí)現(xiàn)鼠標(biāo)拖住3D物體
這篇文章主要為大家詳細(xì)介紹了unity實(shí)現(xiàn)鼠標(biāo)拖住3D物體,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07C#與js實(shí)現(xiàn)去除textbox文本框里面重復(fù)記錄的方法
這篇文章主要介紹了C#與js實(shí)現(xiàn)去除textbox文本框里面重復(fù)記錄的方法,很實(shí)用的功能,需要的朋友可以參考下2014-08-08算法練習(xí)之從String.indexOf的模擬實(shí)現(xiàn)開(kāi)始
這篇文章主要介紹了算法練習(xí)從String.indexOf的模擬實(shí)現(xiàn)開(kāi)始,需要的朋友可以參考下2014-12-12C#客戶(hù)端程序調(diào)用外部程序的3種實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于C#客戶(hù)端程序調(diào)用外部程序的3種實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-04-04