http://www.cnblogs.com/afei-24/p/6757361.html該文講了基於任務的編程,這里再詳細介紹一下。
一.延續任務
private async static void CallerWithAsync() { string result = await GreetingAsync("Stephanie"); Console.WriteLine(result); } static Task<string> GreetingAsync(string name) { return Task.Run<string>(() => { Thread.Sleep(10000); return name; }); } GreetingAsync方法返回一個Task<string>對象。該Task<string>對象包含任務創建的信息,並保存到任務完成。Task類的ContinueWith方法定義了任務完成后就調用的代碼。 private static void CallerWithContinuationTask() { var t1 = GreetingAsync("Stephanie"); t1.ContinueWith(t => { string result = t.Result; Console.WriteLine(result); }); }
由於不使用await,線程不會在方法中等待,會執行完CallerWithContinuationTask()的代碼。不會再ContinueWith這里等待,所以需要一個前台線程,不然會關閉所以線程。
二.同步上下文
CallerWithAsync和CallerWithContinuationTask方法在方法的不同階段使用了不同的線程。
static Task<string> GreetingAsync(string name) { return Task.Run<string>(() => { Thread.Sleep(10000); Console.WriteLine("running greetingasync in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId); return name; }); } private async static void CallerWithAsync() { Console.WriteLine("started CallerWithAsync in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId); string result = await GreetingAsync("Stephanie"); Console.WriteLine(result); Console.WriteLine("finished GreetingAsync in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId); } private static void CallerWithContinuationTask() { Console.WriteLine("started CallerWithContinuationTask in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId); var t1 = GreetingAsync("Stephanie"); t1.ContinueWith(t => { string result = t.Result; Console.WriteLine(result); Console.WriteLine("finished CallerWithContinuationTask in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId); }); }
使用async和await關鍵字,當await完成后,不需要進行任何處理,就能訪問UI線程。默認情況下,生成的代碼就會把線程轉換到擁有同步上下文的線程中。調用異步方法的線程分配給了同步上下文,await完成之后將繼續執行。
如果不使用相同的同步上下文,必須調用Task類的ConfigureAwait(false).例如,一個WPF應用程序,其await后面的代碼沒有任何用到UI元素的代碼。在這種情況下,避免切換到同步上下文會執行的更快。
string s1 = await GreetingAsync("Stephanie").ConfigureAwait(false);
三.使用多個異步方法
在一個異步方法里,可以調用一個或多個異步方法。如何編寫代碼,取決於一個異步方法的結果是否取決於另一個異步方法。
1.按順序調用異步方法
下面的例子,第第二個異步方法依賴於第一個異步方法的結果,await關鍵字就很有用。
private async static void MultipleAsyncMethods()
{
string s1 = await GreetingAsync("Stephanie");
string s2 = await GreetingAsync(s1);
Console.WriteLine("Finished both methods.\n Result 1: {0}\n Result 2: {1}", s1, s2);
}
2.使用組合器
如果第二個異步方法獨立於第一個,每個異步方法可以都不使用await,而是把每個異步方法的返回結果賦值給Task變量,就會運行的更快。
private async static void MultipleAsyncMethodsWithCombinators1()
{
Task<string> t1 = GreetingAsync("Stephanie");
Task<string> t2 = GreetingAsync("Matthias");
await Task.WhenAll(t1, t2);
Console.WriteLine("Finished both methods.\n Result 1: {0}\n Result 2: {1}", t1.Result, t2.Result);
}
Task.WhenAll組合器可以接受多個同一類型的參數,並返回同一類型的值。
Task.WhenAll是在所有傳入方法的任務都完成了才返回Task。
WhenAny是在其中一個傳入方法的任務完成了就返回。
Task.WhenAll定義了幾個重載版本。如果所有的任務返回相同的類型,那么該類型的數組可用於await返回的結果。
string[] result = await Task.WhenAll(t1, t2);
四.轉換異步模式
http://www.cnblogs.com/afei-24/p/6757361.html講了三種異步編程的模式。
並非所有的.NET Framework類在.NET 4.5中都引入了新的異步方法。還有許多類只提供類BeginXXX和EndXXX方法的異步模式,可以使用TaskFactory類的FromAsync方法,它可以把使用異步模式的方法轉換為基於任務的異步模式的方法。
/創建一個委托,並引用同步方法Greeting private static Func<string, string> greetingInvoker = Greeting; static IAsyncResult BeginGreeting(string name, AsyncCallback callback, object state) { return greetingInvoker.BeginInvoke(name, callback, state); } static string EndGreeting(IAsyncResult ar) { return greetingInvoker.EndInvoke(ar); } //FromAsync方法的前兩個參數是委托類型,傳入BeginGreeting, EndGreeting的地址。后兩個參數是輸入的參數和對象狀態參數。 private static async void ConvertingAsyncPattern() { string r = await Task<string>.Factory.FromAsync<string>(BeginGreeting, EndGreeting, "Angela", null); Console.WriteLine(r); }
五.錯誤處理
如果調用異步方法沒有等待,將異步方法放在try/catch中,就不捕獲不到異常。
private static void DontHandle() { try { ThrowAfter(200, "first"); // exception is not caught because this method is finished before the exception is thrown } catch (Exception ex) { Console.WriteLine(ex.Message); } } static async Task ThrowAfter(int ms, string message) { Console.Write("xxx"); await Task.Delay(ms); throw new Exception(message); }
DontHandle方法調用ThrowAfter后,不會在該處等待,會繼續執行,不再保持對ThrowAfter方法的引用。
注意:返回void的異步方法永遠不會等待.異步方法最好返回一個Task類型。
1.異步方法的異常處理
使用await關鍵字,將其放在在try/catch中
private static async void HandleOneError() { try { await ThrowAfter(2000, "first"); } catch (Exception ex) { Console.WriteLine("handled {0}", ex.Message); } }
2.多個異步方法的異常處理
如果按照下面的代碼,第二個異常將不會拋出。因為第一個異常已經拋出,直接調到catch塊內了。
private static async void StartTwoTasks() { try { await ThrowAfter(2000, "first"); await ThrowAfter(1000, "second"); // the second call is not invoked because the first method throws an exception } catch (Exception ex) { Console.WriteLine("handled {0}", ex.Message); } }
使用Task.WhenAll,不管任務是否拋出異常,都會等到兩個任務完成。所以會拋出兩個異常。
但是,只能看見傳遞給Task.WhenAll方法的第一個任務的異常信息,雖然第二個異常會拋出,但不會顯示:
private async static void StartTwoTasksParallel() { Task t1 = null; Task t2 = null; try { t1 = ThrowAfter(2000, "first"); t2 = ThrowAfter(1000, "second"); await Task.WhenAll(t1, t2); } catch (Exception ex) { // just display the exception information of the first task that is awaited within WhenAll Console.WriteLine("handled {0}", ex.Message); } }
3.使用AggregateException信息返回顯示異常
將Task.WhenAll返回的結果寫到一個Task變量中,catch語句只檢索到第一個任務的異常,但可以訪問外部任務taskResult的Exception屬性。Exception屬性是AggregateException類型。這個異常類型定義了InnerExceptions屬性,它包含了等待中的所有異常的列表。
private static async void ShowAggregatedException() { Task taskResult = null; try { Task t1 = ThrowAfter(2000, "first"); Task t2 = ThrowAfter(1000, "second"); await (taskResult = Task.WhenAll(t1, t2)); } catch (Exception ex) { // just display the exception information of the first task that is awaited within WhenAll Console.WriteLine("handled {0}", ex.Message); foreach (var ex1 in taskResult.Exception.InnerExceptions) { Console.WriteLine("inner exception {0} from task {1}", ex1.Message, ex1.Source); } } }
六.取消異步方法
如果后台任務可能運行很長時間,就可能用的取消任務。
取消框架基於協助行為,不是強制性的。一個運行時間很長的任務需要檢查自己是否被取消,在這種情況下,它的工作就是清理所有已打開的資源,並結束相關工作。
取消基於CancellationTokenSource類,該類可用於發送取消請求。請求發送給引用CancellationToken類的任務,其中CancellationToken類和CancellationTokenSource相關聯。
private CancellationTokenSource cts = new CancellationTokenSource(); //添加一個按鈕,用於取消正在運行的任務。使用cts.Cancel(); private void button5_Click(object sender, EventArgs e) { if (cts != null) cts.Cancel(); } private async void button4_Click(object sender, EventArgs e) { string s = await AsyncTaskTest(); } //向Task類的Run方法傳遞CancellationToken參數。但需要檢查是否請求了取消操作。 Task<string> AsyncTaskTest() { return Task.Run(() => { cts.Token.ThrowIfCancellationRequested(); Thread.Sleep(5000); return "異步完成"; } , cts.Token); }