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