C#多線程系列之任務(wù)基礎(chǔ)(三)
TaskAwaiter
先說一下 TaskAwaiter,TaskAwaiter 表示等待異步任務(wù)完成的對象并為結(jié)果提供參數(shù)。
Task 有個(gè) GetAwaiter() 方法,會(huì)返回TaskAwaiter 或TaskAwaiter<TResult>,TaskAwaiter 類型在 System.Runtime.CompilerServices 命名空間中定義。
TaskAwaiter 類型的屬性和方法如下:
屬性:
| 屬性 | 說明 |
|---|---|
| IsCompleted | 獲取一個(gè)值,該值指示異步任務(wù)是否已完成。 |
方法:
| 方法 | 說明 |
|---|---|
| GetResult() | 結(jié)束異步任務(wù)完成的等待。 |
| OnCompleted(Action) | 將操作設(shè)置為當(dāng) TaskAwaiter 對象停止等待異步任務(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();
}另外,我們前面提到過,任務(wù)發(fā)生未經(jīng)處理的異常,任務(wù)被終止,也算完成任務(wù)。
延續(xù)的另一種方法
上一節(jié)我們介紹了 .ContinueWith() 方法來實(shí)現(xiàn)延續(xù),這里我們介紹另一個(gè)延續(xù)方法 .ConfigureAwait()。
.ConfigureAwait() 如果要嘗試將延續(xù)任務(wù)封送回原始上下文,則為 true;否則為 false。
我來解釋一下, .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ù)的方法
前面提到提到過,創(chuàng)建任務(wù)的三種方法:new Task()、Task.Run()、Task.Factory.SatrtNew(),現(xiàn)在來學(xué)習(xí)第四種方法: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),并將其綁定到一個(gè)指定異常上。 |
| 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)并啟用要存儲(chǔ)在取消的任務(wù)中的取消標(biāo)記。 |
| TrySetException(Exception) | 嘗試將基礎(chǔ) Task 轉(zhuǎn)換為 Faulted 狀態(tài),并將其綁定到一個(gè)指定異常上。 |
| TrySetException(IEnumerable) | 嘗試將基礎(chǔ) Task 轉(zhuǎn)換為 Faulted 狀態(tài),并對其綁定一些異常對象。 |
| TrySetResult(TResult) | 嘗試將基礎(chǔ) Task 轉(zhuǎn)換為 RanToCompletion 狀態(tài)。 |
TaskCompletionSource<TResulr> 類可以對任務(wù)的生命周期做控制。
首先要通過 .Task 屬性,獲得一個(gè) 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
// 新開一個(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("開始等待 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ù)的類型
這部分內(nèi)容對 TaskCompletionSource<TResult> 繼續(xù)進(jìn)行講解。
這里我們來設(shè)計(jì)一個(gè)類似 Task 類型的類,支持同步和異步任務(wù)。
- 用戶可以使用
GetResult()同步獲取結(jié)果; - 用戶可以使用
RunAsync()執(zhí)行任務(wù),使用.Result屬性異步獲取結(jié)果;
其實(shí)現(xiàn)如下:
/// <summary>
/// 實(shí)現(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()
{
// 實(shí)例化任務(wù)類
MyTaskClass<string> myTask1 = new MyTaskClass<string>(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(1));
return "www.whuanle.cn";
});
// 直接同步獲取結(jié)果
Console.WriteLine(myTask1.GetResult());
// 實(shí)例化任務(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,它因指定的取消標(biāo)記進(jìn)行的取消操作而完成。
這里筆者抄來了一個(gè)示例:
var token = new CancellationToken(true); Task task = Task.FromCanceled(token); Task<int> genericTask = Task.FromCanceled<int>(token);
網(wǎng)上很多這樣的示例,但是,這個(gè)東西到底用來干嘛的?new 就行了?
帶著疑問我們來探究一下,來個(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ù)。我找了很久,沒有找到很好的示例,如果一個(gè)任務(wù)在開始前就被取消,那么使用 Task.FromCanceled() 是很不錯(cuò)的。
如何在內(nèi)部取消任務(wù)
之前我們討論過,使用 CancellationToken 取消令牌傳遞參數(shù),使任務(wù)取消。但是都是從外部傳遞的,這里來實(shí)現(xiàn)無需 CancellationToken 就能取消任務(wù)。
我們可以使用 CancellationToken 的 ThrowIfCancellationRequested() 方法拋出 System.OperationCanceledException 異常,然后終止任務(wù),任務(wù)會(huì)變成取消狀態(tài),不過任務(wù)需要先傳入一個(gè)令牌。
這里筆者來設(shè)計(jì)一個(gè)難一點(diǎn)的東西,一個(gè)可以按順序執(zhí)行多個(gè)任務(wù)的類。
示例如下:
/// <summary>
/// 能夠完成多個(gè)任務(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>
/// 添加一個(gè)任務(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} 個(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ù)類
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 語句時(shí),會(huì)返回一個(gè) expression,并保留當(dāng)前在代碼中的位置。 下次調(diào)用迭代器函數(shù)時(shí),將從該位置重新開始執(zhí)行。
可以使用 yield break 語句來終止迭代。
官方文檔:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/yield
網(wǎng)上的示例大多數(shù)都是 foreach 的,有些同學(xué)不理解這個(gè)到底是啥意思。筆者這里簡單說明一下。
我們也可以這樣寫一個(gè)示例:
這里已經(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];
}
}但是,同學(xué)又問,這個(gè) return 返回的對象 要實(shí)現(xiàn)這個(gè) 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)用,看看是不是正常運(yùn)行?
static void Main()
{
foreach (var item in ForAsync())
{
Console.WriteLine(item);
}
Console.ReadKey();
}這樣說明了,yield return 返回的對象,并不需要實(shí)現(xiàn) IEnumerable<int> 方法。
其實(shí) 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;
}
}
}它會(huì)自動(dòng)生成 IEnumerable<T> ,而不需要你先實(shí)現(xiàn) IEnumerable<T> 。
補(bǔ)充知識(shí)點(diǎn)
線程同步有多種方法:臨界區(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ǔ)(三)的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
unity實(shí)現(xiàn)鼠標(biāo)拖住3D物體
這篇文章主要為大家詳細(xì)介紹了unity實(shí)現(xiàn)鼠標(biāo)拖住3D物體,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07
C#與js實(shí)現(xiàn)去除textbox文本框里面重復(fù)記錄的方法
這篇文章主要介紹了C#與js實(shí)現(xiàn)去除textbox文本框里面重復(fù)記錄的方法,很實(shí)用的功能,需要的朋友可以參考下2014-08-08
算法練習(xí)之從String.indexOf的模擬實(shí)現(xiàn)開始
這篇文章主要介紹了算法練習(xí)從String.indexOf的模擬實(shí)現(xiàn)開始,需要的朋友可以參考下2014-12-12
C#客戶端程序調(diào)用外部程序的3種實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于C#客戶端程序調(diào)用外部程序的3種實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-04-04

