C#實(shí)現(xiàn)基于任務(wù)的異步編程模式
一.延續(xù)任務(wù)
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方法返回一個(gè)Task<string>對(duì)象。該Task<string>對(duì)象包含任務(wù)創(chuàng)建的信息,并保存到任務(wù)完成。Task類(lèi)的ContinueWith方法定義了任務(wù)完成后就調(diào)用的代碼。 private static void CallerWithContinuationTask() { var t1 = GreetingAsync("Stephanie"); t1.ContinueWith(t => { string result = t.Result; Console.WriteLine(result); }); }
由于不使用await,線程不會(huì)在方法中等待,會(huì)執(zhí)行完CallerWithContinuationTask()的代碼。不會(huì)再ContinueWith這里等待,所以需要一個(gè)前臺(tái)線程,不然會(huì)關(guān)閉所以線程。
二.同步上下文
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關(guān)鍵字,當(dāng)await完成后,不需要進(jìn)行任何處理,就能訪問(wèn)UI線程。默認(rèn)情況下,生成的代碼就會(huì)把線程轉(zhuǎn)換到擁有同步上下文的線程中。調(diào)用異步方法的線程分配給了同步上下文,await完成之后將繼續(xù)執(zhí)行。
如果不使用相同的同步上下文,必須調(diào)用Task類(lèi)的ConfigureAwait(false).例如,一個(gè)WPF應(yīng)用程序,其await后面的代碼沒(méi)有任何用到UI元素的代碼。在這種情況下,避免切換到同步上下文會(huì)執(zhí)行的更快。
string s1 = await GreetingAsync("Stephanie").ConfigureAwait(false);
三.使用多個(gè)異步方法
在一個(gè)異步方法里,可以調(diào)用一個(gè)或多個(gè)異步方法。如何編寫(xiě)代碼,取決于一個(gè)異步方法的結(jié)果是否取決于另一個(gè)異步方法。
1.按順序調(diào)用異步方法
下面的例子,第第二個(gè)異步方法依賴于第一個(gè)異步方法的結(jié)果,await關(guān)鍵字就很有用。
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.使用組合器
如果第二個(gè)異步方法獨(dú)立于第一個(gè),每個(gè)異步方法可以都不使用await,而是把每個(gè)異步方法的返回結(jié)果賦值給Task變量,就會(huì)運(yùn)行的更快。
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組合器可以接受多個(gè)同一類(lèi)型的參數(shù),并返回同一類(lèi)型的值。
- Task.WhenAll是在所有傳入方法的任務(wù)都完成了才返回Task。
- WhenAny是在其中一個(gè)傳入方法的任務(wù)完成了就返回。
Task.WhenAll定義了幾個(gè)重載版本。如果所有的任務(wù)返回相同的類(lèi)型,那么該類(lèi)型的數(shù)組可用于await返回的結(jié)果。
string[] result = await Task.WhenAll(t1, t2);
四.轉(zhuǎn)換異步模式
http://chabaoo.cn/article/244023.htm講了三種異步編程的模式。
并非所有的.NET Framework類(lèi)在.NET 4.5中都引入了新的異步方法。還有許多類(lèi)只提供類(lèi)BeginXXX和EndXXX方法的異步模式,可以使用TaskFactory類(lèi)的FromAsync方法,它可以把使用異步模式的方法轉(zhuǎn)換為基于任務(wù)的異步模式的方法。
/創(chuàng)建一個(gè)委托,并引用同步方法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方法的前兩個(gè)參數(shù)是委托類(lèi)型,傳入BeginGreeting, EndGreeting的地址。后兩個(gè)參數(shù)是輸入的參數(shù)和對(duì)象狀態(tài)參數(shù)。 private static async void ConvertingAsyncPattern() { string r = await Task<string>.Factory.FromAsync<string>(BeginGreeting, EndGreeting, "Angela", null); Console.WriteLine(r); }
五.錯(cuò)誤處理
如果調(diào)用異步方法沒(méi)有等待,將異步方法放在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方法調(diào)用ThrowAfter后,不會(huì)在該處等待,會(huì)繼續(xù)執(zhí)行,不再保持對(duì)ThrowAfter方法的引用。
注意:返回void的異步方法永遠(yuǎn)不會(huì)等待.異步方法最好返回一個(gè)Task類(lèi)型。
1.異步方法的異常處理
使用await關(guān)鍵字,將其放在在try/catch中
private static async void HandleOneError() { try { await ThrowAfter(2000, "first"); } catch (Exception ex) { Console.WriteLine("handled {0}", ex.Message); } }
2.多個(gè)異步方法的異常處理
如果按照下面的代碼,第二個(gè)異常將不會(huì)拋出。因?yàn)榈谝粋€(gè)異常已經(jīng)拋出,直接調(diào)到catch塊內(nèi)了。
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,不管任務(wù)是否拋出異常,都會(huì)等到兩個(gè)任務(wù)完成。所以會(huì)拋出兩個(gè)異常。
但是,只能看見(jiàn)傳遞給Task.WhenAll方法的第一個(gè)任務(wù)的異常信息,雖然第二個(gè)異常會(huì)拋出,但不會(huì)顯示:
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返回的結(jié)果寫(xiě)到一個(gè)Task變量中,catch語(yǔ)句只檢索到第一個(gè)任務(wù)的異常,但可以訪問(wèn)外部任務(wù)taskResult的Exception屬性。Exception屬性是AggregateException類(lèi)型。這個(gè)異常類(lèi)型定義了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); } } }
六.取消異步方法
如果后臺(tái)任務(wù)可能運(yùn)行很長(zhǎng)時(shí)間,就可能用的取消任務(wù)。
取消框架基于協(xié)助行為,不是強(qiáng)制性的。一個(gè)運(yùn)行時(shí)間很長(zhǎng)的任務(wù)需要檢查自己是否被取消,在這種情況下,它的工作就是清理所有已打開(kāi)的資源,并結(jié)束相關(guān)工作。
取消基于CancellationTokenSource類(lèi),該類(lèi)可用于發(fā)送取消請(qǐng)求。請(qǐng)求發(fā)送給引用CancellationToken類(lèi)的任務(wù),其中CancellationToken類(lèi)和CancellationTokenSource相關(guān)聯(lián)。
private CancellationTokenSource cts = new CancellationTokenSource(); //添加一個(gè)按鈕,用于取消正在運(yùn)行的任務(wù)。使用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類(lèi)的Run方法傳遞CancellationToken參數(shù)。但需要檢查是否請(qǐng)求了取消操作。 Task<string> AsyncTaskTest() { return Task.Run(() => { cts.Token.ThrowIfCancellationRequested(); Thread.Sleep(5000); return "異步完成"; } , cts.Token); }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#使用Win32?Api實(shí)現(xiàn)進(jìn)程注入到wechat的過(guò)程
這篇文章主要介紹了C#使用Win32?Api實(shí)現(xiàn)進(jìn)程注入到wechat,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09將excel數(shù)據(jù)轉(zhuǎn)換成dataset示例
這篇文章主要介紹了不借助第三方插件的情況下將Excel中的數(shù)據(jù)轉(zhuǎn)換成DataSet的方法,需要的朋友可以參考下2014-02-02C#數(shù)據(jù)類(lèi)型實(shí)現(xiàn)背包、隊(duì)列和棧
本文詳細(xì)講解了C#數(shù)據(jù)結(jié)構(gòu)類(lèi)型,并實(shí)現(xiàn)背包、隊(duì)列和棧的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04picturebox加載圖片的三種方法與網(wǎng)站驗(yàn)證碼的抓取
這篇文章主要介紹了picturebox加載圖片的三種方法與網(wǎng)站驗(yàn)證碼的抓取,需要的朋友可以參考下2015-03-03C# string格式的日期時(shí)間字符串轉(zhuǎn)為DateTime類(lèi)型的方法
這篇文章主要介紹了C# string格式的日期時(shí)間字符串轉(zhuǎn)為DateTime類(lèi)型的方法,需要的朋友可以參考下2017-02-02C#中WPF內(nèi)存回收與釋放LierdaCracker的實(shí)現(xiàn)
本文主要介紹了C#中WPF內(nèi)存回收與釋放LierdaCracker的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07C#限速下載網(wǎng)絡(luò)文件的方法實(shí)例
本篇文章主要介紹了C#限速下載網(wǎng)絡(luò)文件的方法實(shí)例,可以限制下載文件的速度,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2016-12-12