異步編程和線程處理是併發或並行編程很是重要的功能特徵。爲了實現異步編程,可以使用線程也能夠不用。將異步與線程同時講,將有助於咱們更好的理解它們的特徵。前端
本文中涉及關鍵知識點編程
1. 異步編程後端
2. 線程的使用數組
3. 基於任務的異步模式多線程
4. 並行編程併發
5. 總結app
什麼是異步操做?異步操做是指某些操做可以獨立運行,不依賴主流程或主其餘處理流程。一般狀況下,C#程序從Main方法開始,當Main方法返回時結束。全部的操做都是按順序執行的。執行操做是有序列的,一個操做必須等到其前面的操做完成纔可以執行。如如下代碼示例:框架
1: static void Main(string[] args)
2:
3: {
4:
5: DoTaskOne();
6:
7: DoTaskTwo();
8:
9: }
「DoTaskOne」方法結束後,DoTaskTwo()纔可以執行。異步
異步編程中經常使用後臺運行的方法體現,主調用線程不會被阻塞。調用後臺運行的方法後,執行流程會當即返回到調用的線程並繼續執行其餘任務。後臺運行方法一般是用線程或任務來實現。async
在上面的例子中,在「DoTaskOne」方法調用成功後,若是「DoTaskOne」是異步調用,,執行流程當即返回到Main方法中,並繼續執行「DoTaskTwo」 方法。
C#提供了Thread類建立線程實現異步編程,或者使用.NET提供的異步模式實現異步編程。.NET中提供了三種不一樣的異步模式:
1. 異步編程模型(APM)模式
2. 基於事件的異步模式(EAP)
3. 基於任務的異步模式(TAP)
前兩種模型微軟官方並不推薦使用,本文再也不詳細描述。咱們將詳細討論基於任務的異步模式(TAP):
在.NET 4.5中引入了異步編程模式,大部分狀況下都不須要咱們手動建立線程。編譯器已經替代了開發人員來完成這項工做。
建立新線程是很是耗時的。通常狀況下,異步和並行編程使用 「基於任務的異步模式(TAP)」和「任務並行庫(TPL)」就夠了。若是須要控制線程的功能則須要使用其餘模式。
TAP和TPL都是基於任務。通常來講任務是從線程池中調用線程( 線程池是.NET框架建立的和維護的線程集。若是咱們使用任務,就不須要直接調用線程池。
任務能夠在如下狀況運行:
1. 在正在運行的線程中
2. 在新線程中
3. 從線程池中的某一線程中
4. 沒有線程也能夠運行
若是使用任務機制,開發人員就沒必要擔憂線程的建立或使用,.NET框架已經爲咱們解決了這一難題。
有時候須要控制線程,執行如下操做:
1. 設置線程名稱
2. 設置線程優先級
3. 設置線程是前端或後端運行
咱們可使用線程類來建立線程。
Thread類的構造函數接收委託類型的參數
1. ThreadStart:定義了返回值爲空的方法,且不帶參數的方法。
2. ParameterizedThreadStart:定義了返回值爲空且有一個object類型的參數。
下面是一個簡單的例子,使用 Start方法啓動一個新線程:
1: static void Main(string[] args)
2:
3: {
4:
5: Thread thread = new Thread(DoTask);
6:
7: thread.Start();// Start DoTask method in a new thread
8:
9: //Do other tasks in main thread
10:
11: }
12:
13: static public void DoTask() {
14:
15: //do something in a new thread
16:
17: }
能夠用Lamda表達式代替線程名稱:
1: static void Main(string[] args)
2:
3: {
4:
5: Thread thread = new Thread(() => {
6:
7: //do something in a new thread
8:
9: });
10:
11: thread.Start();// Start a new thread
12:
13: //Do other tasks in main thread
14:
15: }
若是不須要引用變量,可以下直接啓動線程:
1: static void Main(string[] args)
2:
3: {
4:
5: new Thread(() => {
6:
7: //do something in a new thread
8:
9: }).Start();// Start a new thread
10:
11: //Do other tasks in main thread
12:
13: }
可是,若是想控制線程對象,對線程設置一些屬性,須要在線程建立後引用線程變量。以下可給線程對象的不一樣屬性設值:
1: static void Main(string[] args)
2:
3: {
4:
5: Thread thread = new Thread(DoTask);
6:
7: thread.Name = "My new thread";// Asigning name to the thread
8:
9: thread.IsBackground = false;// Made the thread forground
10:
11: thread.Priority = ThreadPriority.AboveNormal;// Setting thread priority
12:
13: thread.Start();// Start DoTask method in a new thread
14:
15: //Do other task in main thread
16:
17: }
調用引用變量,能夠執行一些操做如停止線程或經過調用join方法等待阻塞線程。
若是須要經過函數傳值,能夠給Start方法傳值。因爲該方法的參數爲Object類型,所以須要強制轉換類型。
1: static void Main(string[] args)
2:
3: {
4:
5: Thread thread = new Thread(DoTaskWithParm);
6:
7: thread.Start("Passing string");// Start DoTaskWithParm method in a new thread
8:
9: //Do other task in main thread
10:
11: }
12:
13: static public void DoTaskWithParm(object data)
14:
15: {
16:
17: //we need to cast the data to appropriate object
18:
19: }
.NET框架引入了兩個新的關鍵字來實現異步編程:「async」和「await」。使用 「await」的異步方法必須由「async」修飾符來聲明方法。「await」關鍵字修飾調用異步方法。await 運算符應用於一個異步方法中的任務以掛起該方法的執行,直到等待任務完成.以下:
1: private async static void CallerWithAsync()// async modifier is used
2:
3: {
4:
5: string result = await GetSomethingAsync();// await is used before a method call. It suspends
6: //execution of CallerWithAsync() method and control returs to the calling thread that can
//perform other task.
7:
8: Console.WriteLine(result);
9: // this line would not be executed before GetSomethingAsync() //method completes
10:
11: }
而「 async 」修飾符只能用於返回值爲Task類型或Void的方法。它不能用於主程序的切入點。
全部的方法以前不能使用await關鍵字,使用「await」關鍵字方法必須返回 「可等待」類型。如下屬於「可等待」類型:
1. Task
2. Task<T>
3. 自定義「可等待」類型。
首先咱們須要聲明一個返回類型爲Task或Task<T>的異步方法。能夠經過如下幾種方式建立任務:
1. Task.Factory.StartNew方法:在以前的.NET版本(在.NET 4中),是建立和啓動任務的主要方法。
2. Task.Run或Task.Run <T>方法:從.NET 4.5這個方法已經被使用。此方法足以知足常見狀況。
3. Task.FromResult方法:若是結果是已計算,就能夠用這個方法來建立任務。
使用Task.Run <T>方法建立Task。該方法將特定工做按順序排列在線程池中運行,並返回工做的任務句柄。須要如下步驟從同步方法中建立異步任務:
1. 假設下面方法是同步的,但須要必定的時間來完成:
1: static string Greeting(string name)
2:
3: {
4:
5: Thread.Sleep(3000);
6:
7: return string.Format("Hello, {0}", name);
8:
9: }
2. 要以異步方式訪問此方法,必須以異步方式封裝。命名爲「GreetingAsync」。增長「Async」的後綴命名異步方法。
1: static Task<string> GreetingAsync(string name)
2:
3: {
4:
5: return Task.Run<string>(() =>
6:
7: {
8:
9: return Greeting(name);
10:
11: });
12:
13: }
3.如今,可經過使用的await關鍵字調用異步方法GreetingAsync
1: private async static void CallWithAsync()
2:
3: {
4:
5: //some other tasks
6:
7: string result = await GreetingAsync("Bulbul");
8:
9: //We can add multiple 「await」 in same 「async」 method
10:
11: //string result1 = await GreetingAsync(「Ahmed」);
12:
13: //string result2 = await GreetingAsync(「Every Body」);
14:
15: Console.WriteLine(result);
16:
17: }
當「CallWithAsync」方法被調用時,與常規的同步方法同樣執行,直到遇到「await」的關鍵字。當它執行到 await的關鍵字會處理執行,並開始等待「GreetingAsync(」 Bulbul 「)」 方法被完成。同時,程序流將返回」 CallWithAsync 「方法的調用者,並繼續執行調用者的任務。
當「GreetingAsync(" Bulbul ") 方法完成,「CallWithAsync」的方法恢復 「await關鍵字後的其餘任務。在本實例中,將繼續執行的代碼「Console.WriteLine(result)」
4. 使用任務持續:Task類 「ContinueWith」的方法定義了Task完成後被調用的代碼。
1: private static void CallWithContinuationTask()
2:
3: {
4:
5: Task<string> t1 = GreetingAsync("Bulbul");
6:
7: t1.ContinueWith(t =>
8:
9: {
10:
11: string result = t.Result;
12:
13: Console.WriteLine(result);
14:
15: });
16:
17: }
若是使用「ContinueWith」的方法就不須要使用「await「關鍵字,編譯器會自動在合適的位置中添加「await」關鍵字。
等候多個異步方法。
看看下面的代碼:
1: private async static void CallWithAsync()
2:
3: {
4:
5: string result = await GreetingAsync("Bulbul");
6:
7: string result1 = await GreetingAsync(“Ahmed”);
8:
9: Console.WriteLine(result);
10:
11: Console.WriteLine(result1);
12:
13: }
有兩個正在等待調用函數序列。「GreetingAsync(」 Ahmed 「)」 會在完成第一個呼叫「GreetingAsync(」 Bulbul 「)」 以後啓動。若是「result」和上面的代碼「result1」是獨立的,那麼連續的「awiating」並非一個好的作法。
在這種狀況下,咱們能夠簡化調用方法,不須要添加多個「await」關鍵字,只在一個地方添加await關鍵字,以下所示,這種狀況下,該方法的調用均可以並行執行。
1: private async static void MultipleAsyncMethodsWithCombinators()
2:
3: {
4:
5: Task<string> t1 = GreetingAsync("Bulbul");
6:
7: Task<string> t2 = GreetingAsync("Ahmed");
8:
9: await Task.WhenAll(t1, t2);
10:
11: Console.WriteLine("Finished both methods.\n " +
12:
13: "Result 1: {0}\n Result 2: {1}", t1.Result, t2.Result);
14:
15: }
在這裏,咱們使用Task.WhenAll鏈接器。Task.WhenAll建立一個任務,將完成全部的提供的任務。Task類也有其餘的結合器。Task.WhenAny,當所任務鏈中全部的任務完成時,結束使用。
必須把「await的代碼塊放在try塊內捕獲異常。
1: private async static void CallWithAsync()
2:
3: {
4:
5: try
6:
7: {
8:
9: string result = await GreetingAsync("Bulbul");
10:
11: }
12:
13: catch (Exception ex)
14:
15: {
16:
17: Console.WriteLine(“handled {0}”, ex.Message);
18:
19: }
20:
21: }
若是try塊中有多個「await」,只有第一個」 await「異常會被處理,其餘「await」將沒法被捕捉。若是但願全部的方法都能捕獲異常,不能使用「await」關鍵字調用方法,使用Task.WhenAll來執行任務。
1: private async static void CallWithAsync()
2:
3: {
4:
5: try
6:
7: {
8:
9: Task<string> t1 = GreetingAsync("Bulbul");
10:
11: Task<string> t2 = GreetingAsync("Ahmed");
12:
13: await Task.WhenAll(t1, t2);
14:
15: }
16:
17: catch (Exception ex)
18:
19: {
20:
21: Console.WriteLine(“handled {0}”, ex.Message);
22:
23: }
24:
25: }
捕獲全部任務的錯誤一種方法是在try塊以外聲明任務,這樣能夠從try塊進行訪問,並檢查任務的「IsFaulted」屬性。若是它存在異常那麼「IsFaulted」屬性值爲True,就可捕獲任務實例的內部異常。
還有另外一個更好的辦法:
1: static async void ShowAggregatedException()
2:
3: {
4:
5: Task taskResult = null;
6:
7: try
8:
9: {
10: Task<string> t1 = GreetingAsync("Bulbul");
11:
12: Task<string> t2 = GreetingAsync("Ahmed");
13:
14: await (taskResult = Task.WhenAll(t1, t2));
15:
16: }
17:
18: catch (Exception ex)
19:
20: {
21:
22: Console.WriteLine("handled {0}", ex.Message);
23:
24: foreach (var innerEx in taskResult.Exception.InnerExceptions)
25:
26: {
27: Console.WriteLine("inner exception {0}", nnerEx.Message); }
28: }
29:
30: }
在此以前,若是從線程池中調用線程,線程是不可能取消。如今,Task類提供了一個方法基於CancellationTokenSource類可以取消已啓動的任務,取消任務步驟:
1. 異步方法應該除外 「 CancellationToken」 參數類型
2. 建立CancellationTokenSource類實例:
var cts =new CancellationTokenSource();
3. 傳遞CancellationToken,如:
1: Task<string> t1 = GreetingAsync("Bulbul", cts.Token);
4. 長時間運行的方法中,必須調用CancellationToken 的ThrowIfCancellationRequested()方法。
1: static string Greeting(string name, CancellationToken token){
2:
3: Thread.Sleep(3000);
4:
5: token. ThrowIfCancellationRequested();
6:
7: return string.Format("Hello, {0}", name);
8:
}
5. 從等待的Task中捕獲 OperationCanceledException異常。
6. 若是經過調用CancellationTokenSource的實例的方法執行取消操做,將從長時間運行操做中拋出OperationCanceledException異常。也能夠設置取消的時間。如下是完整的代碼,一秒後執行取消操做:
1: static void Main(string[] args)
2:
3: {
4: CallWithAsync();
5:
6: Console.ReadKey();
7:
8: }
9:
10:
11: async static void CallWithAsync()
12:
13: {
14:
15: try
16:
17: {
18:
19: CancellationTokenSource source = new CancellationTokenSource();
20:
21: source.CancelAfter(TimeSpan.FromSeconds(1));
22:
23: var t1 = await GreetingAsync("Bulbul", source.Token);
24: }
25:
26: catch (OperationCanceledException ex)
27:
28: {
29:
30: Console.WriteLine(ex.Message);
31:
32: }
33:
34: }
35:
36: static Task<string> GreetingAsync(string name, CancellationToken token)
37:
38: {
39:
40: return Task.Run<string>(() =>
41:
42: {
43:
44: return Greeting(name, token);
45:
46: });
47: }
48:
49:
50: static string Greeting(string name, CancellationToken token)
51:
52: {
53:
54: Thread.Sleep(3000);
55: token.ThrowIfCancellationRequested();
56:
57: return string.Format("Hello, {0}", name);
58:
59: }
60:
.NET 4.5及以上版本推出「Parallel類,是線程類的抽象。使用「Parallel」類,咱們能夠實現並行。並行與線程不一樣,它使用全部可用的CPU或內核的。如下兩種類型的並行是可行:
數據並行:若是咱們有數據的大集合,咱們但願在每一個數據的某些操做進行並行使用,那麼就可使用數據並行。Parallel類有靜態For或ForEach來執行數據並行行,如
1: ParallelLoopResult result =
2: Parallel.For(0, 100, async (int i) =>
3: {
4: Console.WriteLine("{0}, task: {1}, thread: {2}", i,
5: Task.CurrentId, Thread.CurrentThread.ManagedThreadId);
6: await Task.Delay(10);
7:
8: });
For或ForEach方法能夠在多線程中和且索引無序能夠是無序的。
若是想中止並行For或ForEach方法,可經過ParallelLoopState做爲參數,並根據須要打破循環的狀態,跳出循環。
1: ParallelLoopResult result =
2: Parallel.For(0, 100, async (int i, ParallelLoopState pls) =>
3: {
4: Console.WriteLine("{0}, task: {1}, thread: {2}", i,
5: Task.CurrentId, Thread.CurrentThread.ManagedThreadId);
6: await Task.Delay(10);
7: if (i > 5) pls.Break();
8: });
2. 任務並行:若是想要同時運行多個任務的,咱們能夠經過調用Parallel類的invoke方法使用任務並行Parallel.Invoke方法接收委託行爲的數組。例如:
1: static void ParallelInvoke()
2:
3: {
4:
5: Parallel.Invoke(MethodOne, MethodTwo);
6:
7: }
8:
本文詳細介紹了.NET Framework 4.5提供的異步編程技術及細節。
原文連接:http://www.codeproject.com/Articles/996857/Asynchronous-programming-and-Threading-in-Csharp-N