前言
今天周五,早上起床晚了。趕著擠公交上班。但是目前眼前有這么幾件事情。刷牙洗臉、泡牛奶、煎蛋。在同步編程眼中。先刷牙洗臉,然后燒水泡牛奶。再煎蛋,最后喝牛奶吃蛋。毫無(wú)疑問(wèn),在時(shí)間緊促的當(dāng)下。它完了,穩(wěn)的遲到、半天工資沒(méi)了。那么異步編程眼中,或許還有一絲解救的希望。先燒水,同時(shí)刷牙洗臉。然后泡牛奶,等牛奶不那么燙的時(shí)候煎個(gè)蛋。最后喝牛奶吃蛋。也許還能不遲到。在本篇文章中將圍繞這個(gè)事例講解異步編程。
異步編程不同模式
在看異步模式之前我們先看一個(gè)同步調(diào)用的事例:
?
class Program { private const string url = "http://www.cninnovation.com/";
static void Main(string[] args) { AsyncTest(); } public static void AsyncTest()
{ Console.WriteLine(nameof(AsyncTest));using (var client=new WebClient()) {
string content = client.DownloadString(url);
Console.WriteLine(content.Substring(0,100)); } Console.WriteLine(); } }
?
在這個(gè)事例中,DownloadString方法將請(qǐng)求的地址下載為string資源,但是在我們實(shí)際運(yùn)行當(dāng)中,因?yàn)镈ownloadString
方法阻塞調(diào)用線程,直到返回結(jié)果。整個(gè)程序就一直卡在了DownloadString
方法這里。這樣的體驗(yàn)是非常的不愉快的。有了問(wèn)題,自然也就有了對(duì)應(yīng)的解決方法,下面我們就一起來(lái)看看對(duì)應(yīng)的解決方法的進(jìn)步史吧。
一、異步模式
異步模式是處理異步特性的第一種方式,它不僅可以使用幾個(gè)API,還可以使用基本功能(如委托類(lèi)型)。不過(guò)這里需要注意的是在使用.NET
Core調(diào)用委托的這些方法時(shí),會(huì)拋出一個(gè)異常,其中包含平臺(tái)不支持的信息。
異步模式定義了BeginXXX方法和EndXXX方法
。例如上面同步方法是DownloadString,那么異步就是BeginDownloadString和EndDownloadString方法。
BeginXXX方法接收其同步方法的所有輸入的參數(shù),EndXXX方法使用同步方法所有的輸出參數(shù),并按照同步方法的返回類(lèi)型來(lái)返回結(jié)果。BeginXXX定義了一個(gè)AsyncCallback參數(shù),用于接受在異步方法執(zhí)行完成后調(diào)用的委托。BeginXXX方法返回IAsyncResult,用于驗(yàn)證調(diào)用是否已經(jīng)完成,并且一直等到方法執(zhí)行結(jié)束。
我們看下異步模式的事例,因?yàn)樯厦媸吕械腤ebClient沒(méi)有異步模式的實(shí)現(xiàn),這里我們使用WebRequest來(lái)代替:
?
class Program { private const string url = "http://www.cninnovation.com/";
static void Main(string[] args) { AsyncTest(); } public static void AsyncTest()
{ Console.WriteLine(nameof(AsyncTest)); WebRequest request=
WebRequest.Create(url); IAsyncResult result=
request.BeginGetResponse(ReadResponse,null); Console.ReadLine(); void
ReadResponse(IAsyncResult ar) {using (WebResponse response =
request.EndGetResponse(ar)) { Stream stream= response.GetResponseStream(); var
reader =new StreamReader(stream); string content = reader.ReadToEnd();
Console.WriteLine(content.Substring(0, 100)); Console.WriteLine(); } } } }
上面事例中展現(xiàn)了異步調(diào)用的一種方式---使用異步模式。先使用WebRequest類(lèi)的Create方法創(chuàng)建WebRequest,然后使用
BeginGetResponse方法異步將請(qǐng)求發(fā)送到服務(wù)器。調(diào)用線程沒(méi)有被阻塞。第一個(gè)參數(shù)上面有講,完成后回調(diào)的委托。一旦網(wǎng)絡(luò)請(qǐng)求完成,就會(huì)調(diào)用該方法。
在UI應(yīng)用程序中使用異步模式有一個(gè)問(wèn)題:回調(diào)的委托方法沒(méi)有在UI線程中允許,因此如果不切換到UI,就不能訪問(wèn)UI元素的成員,而是拋出一個(gè)異常。調(diào)用線程不能訪問(wèn)這個(gè)對(duì)象,因?yàn)榱硪粋€(gè)線程擁有它。為了簡(jiǎn)化這個(gè)過(guò)程在.NET
Framework 2.0 中引入了基于時(shí)間的異步模式,這樣更好的解決了此問(wèn)題,下面就介紹基于事件的異步模式。
二、基于事件的異步模式
基于事件的異步模式定義了一個(gè)帶有”Async”后綴的方法。下面看下如何使用這個(gè)基于事件的異步模式,還是使用的第一個(gè)事例進(jìn)行修改。
class Program { private const string url = "http://www.cninnovation.com/";
static void Main(string[] args) { AsyncTest(); } public static void AsyncTest()
{ Console.WriteLine(nameof(AsyncTest));using (var client =new WebClient()) {
client.DownloadStringCompleted+= (sender, e) => {
Console.WriteLine(e.Result.Substring(0,100)); }; client.DownloadStringAsync(new
Uri(url)); Console.ReadLine(); } } }
在上述事例中,對(duì)于同步方法DownloadString,提供了一個(gè)異步變體方法DownloadStringAsync。當(dāng)請(qǐng)求完成時(shí)會(huì)觸發(fā)DownloadStringCompleted
事件,關(guān)于事件使用及描述前面文章已有詳細(xì)介紹了。這個(gè)事件類(lèi)型一共帶有兩個(gè)參數(shù)一個(gè)是object類(lèi)型,一個(gè)是DownloadStringCompletedEventArgs類(lèi)型。后面?zhèn)€這個(gè)類(lèi)型通過(guò)Result屬性返回結(jié)果字符串。
?
?
?
?
這里使用的DownloadStringCompleted
事件,事件處理成將通過(guò)保存同步上下文的線程來(lái)調(diào)用,在應(yīng)用程序中這就是UI線程,因此可以直接訪問(wèn)UI元素。這里就是與上面那個(gè)異步模式相比更優(yōu)之處。下面我們看看基于事件的異步模式進(jìn)一步的改進(jìn)將是什么樣的————基于任務(wù)的異步模式。
三、基于任務(wù)的異步模式
在.NET Framework
4.5中更新了WebClient類(lèi),也新增提供了基于任務(wù)的異步模式,該模式也定義了一個(gè)”Async”后綴的方法,返回一個(gè)Task類(lèi)型,但是由于基于事件的異步模式已經(jīng)采用了,所以更改為——DownloadStringTaskAsync。
DownloadStringTaskAsync方法聲明返回為T(mén)ask<string>,但是不需要一個(gè)Task<string>類(lèi)型的變量接收返回結(jié)果,只需要聲明一個(gè)string類(lèi)型的變量。并且使用await關(guān)鍵字。此關(guān)鍵字會(huì)解除線程的阻塞,去完成其他的任務(wù)。我們看下面這個(gè)事例
class Program { private const string url = "http://www.cninnovation.com/";
static async Task Main(string[] args) { await AsyncTestTask(); } public static
async Task AsyncTestTask() { Console.WriteLine("當(dāng)前任務(wù)Id是:"+
Thread.CurrentThread.ManagedThreadId); Console.WriteLine(nameof(AsyncTestTask));
using (var client = new WebClient()) { string content = await
client.DownloadStringTaskAsync(url); Console.WriteLine("當(dāng)前任務(wù)Id是:"+
Thread.CurrentThread.ManagedThreadId); Console.WriteLine(content.Substring(0,100
)); Console.ReadLine(); } } }
?
?
?
上面代碼相對(duì)于之前的就較為簡(jiǎn)單多了,并且也沒(méi)有阻塞,不用切換回UI線程。調(diào)用順序也和同步方法一樣。
這里我單獨(dú)的放出了允許結(jié)果,新增了當(dāng)前任務(wù)顯示,在剛進(jìn)入方法時(shí)任務(wù)為1,但是執(zhí)行完成DownloadStringTaskAsync方法后,任務(wù)id變成了8,上面其他的事例允許此代碼也都是返回任務(wù)id為1,這也就是基于任務(wù)的異步模式的不同點(diǎn)。
?
?
異步編程的基礎(chǔ)
async和await關(guān)鍵字編譯器功能,編譯器會(huì)用Task類(lèi)創(chuàng)建代碼。如果不使用這兩個(gè)關(guān)鍵字,也是可以用c#4.0Task類(lèi)的方法來(lái)實(shí)現(xiàn)同樣的功能,雖然會(huì)麻煩點(diǎn)。下面我們看下async和await這兩個(gè)關(guān)鍵字能做什么,如何采用簡(jiǎn)單的方式創(chuàng)建異步方法,如何并行調(diào)用多個(gè)異步方法等等。
這里我們首先創(chuàng)建一個(gè)觀察線程和任務(wù)的方法,來(lái)更好的觀察理解發(fā)送的變化。
??
public static void SeeThreadAndTask(string info) { string taskinfo =
Task.CurrentId ==null ? "沒(méi)任務(wù)" : "任務(wù)id是:" + Task.CurrentId; Console.WriteLine($"
{info} 在線程{Thread.CurrentThread.ManagedThreadId}和{taskinfo}中執(zhí)行"); }
?
同時(shí)準(zhǔn)備了一個(gè)同步方法,該方法使用Delay方法等待一段時(shí)間后返回一個(gè)字符串。
static void Main(string[] args) { var name= GetString("張三");
Console.WriteLine(name); }static string GetString(string name) {
SeeThreadAndTask($"運(yùn)行{nameof(GetString)}"); Task.Delay(3000).Wait(); return $"
你好,{name}"; }
?
一、創(chuàng)建任務(wù)
上面我們也說(shuō)了不使用哪兩個(gè)關(guān)鍵字也可以使用Task類(lèi)實(shí)現(xiàn)同樣的功能,這里我們采用一個(gè)簡(jiǎn)單的做大,使用Task.Run方法返回一個(gè)任務(wù)。
? ? ? ??
static void Main(string[] args) { SeeThreadAndTask($"運(yùn)行{nameof(Main)}"); var
name= GetStringAsync("張三"); Console.WriteLine(name.Result); Console.ReadLine();
}static Task<string> GetStringAsync(string name) => Task.Run<string>(() => {
SeeThreadAndTask($"運(yùn)行{nameof(GetStringAsync)}"); return GetString(name); });
?
二、調(diào)用異步方法
我們繼續(xù)來(lái)看await和async關(guān)鍵字,使用await關(guān)鍵字調(diào)用返回任務(wù)的異步方法,但是也需要使用async修飾符。
static void Main(string[] args) { SeeThreadAndTask($"運(yùn)行{nameof(Main)}");
GetSelfAsync("張三"); Console.ReadLine(); } private static async void
GetSelfAsync(string name) { SeeThreadAndTask($"開(kāi)始運(yùn)行{nameof(GetSelfAsync)}");
string result =await GetStringAsync(name); Console.WriteLine(result);
SeeThreadAndTask($"結(jié)束運(yùn)行{nameof(GetSelfAsync)}"); }
?
在異步方法完成前,該方法內(nèi)的其他代碼不會(huì)執(zhí)行。但是,啟動(dòng)GetSelfAsync方法的線程可以被重用。該線程沒(méi)有被阻塞。
這里剛開(kāi)始時(shí)候中是沒(méi)有任務(wù)執(zhí)行的,GetStringAsync方法開(kāi)始在一個(gè)任務(wù)中執(zhí)行,這里所在的線程也是不同的。其中GetString和GetStringAsync方法都執(zhí)行完畢,等待之后返回現(xiàn)在GetStringAsync開(kāi)始轉(zhuǎn)變?yōu)榫€程3,同時(shí)也沒(méi)有任務(wù)。await確保任務(wù)完成后繼續(xù)執(zhí)行,但是現(xiàn)在使用的是另一個(gè)線程。這一個(gè)行為在我們使用控制臺(tái)應(yīng)用程序和具有同步上下文的應(yīng)用程序之間是不同的。
三、使用Awaiter
可以對(duì)任何提供GetAwaiter方法并對(duì)awaiter的對(duì)象async關(guān)鍵字。其中awaiter用OnCompleted方法實(shí)現(xiàn)INotifyCompletion接口,完成任務(wù)時(shí)調(diào)用,下面事例中沒(méi)有使用await關(guān)鍵字,而是使用GetAwaiter方法,返回一個(gè)TaskAwaiter,并且使用OnCompleted方法,分配一個(gè)在任務(wù)完成時(shí)調(diào)用的本地函數(shù)。
static void Main(string[] args) { SeeThreadAndTask($"運(yùn)行{nameof(Main)}");
GetSelfAwaiter("張三"); Console.ReadLine(); } private static void GetSelfAwaiter(
string name) { SeeThreadAndTask($"運(yùn)行{nameof(GetSelfAwaiter)}"); TaskAwaiter<
string> awaiter = GetStringAsync(name).GetAwaiter();
awaiter.OnCompleted(OnCompletedAwauter);void OnCompletedAwauter() {
Console.WriteLine(awaiter.GetResult()); SeeThreadAndTask($"
運(yùn)行{nameof(GetSelfAwaiter)}"); } }
?
?
我們看這個(gè)運(yùn)行結(jié)果,再與上面調(diào)用異步方法的運(yùn)行結(jié)果進(jìn)行對(duì)比,好像類(lèi)似于使用await關(guān)鍵字的情形。相當(dāng)于編譯器把a(bǔ)wait關(guān)鍵字后面的所有的代碼放進(jìn)OnCompleted方法的代碼塊中完成。當(dāng)然也可另外方法使用GetAwaiter方法。
?
static void Main(string[] args) { SeeThreadAndTask($"運(yùn)行{nameof(Main)}");
GetSelfAwaiter("張三"); Console.ReadLine(); } private static void GetSelfAwaiter(
string name) { SeeThreadAndTask($"運(yùn)行{nameof(GetSelfAwaiter)}"); string awaiter =
GetStringAsync(name).GetAwaiter().GetResult(); Console.WriteLine(awaiter);
SeeThreadAndTask($"運(yùn)行{nameof(GetSelfAwaiter)}"); }
?
?
四、延續(xù)任務(wù)
這里我們介紹使用Task對(duì)象的特性來(lái)處理任務(wù)的延續(xù)。GetStringAsync方法返回一個(gè)Task<string>對(duì)象包含了任務(wù)創(chuàng)建的一些信息,并一直保存到任務(wù)完成。Task類(lèi)的ContinueWith定義了完成任務(wù)之后就調(diào)用的代碼。這里指派給ContinueWith方法的委托接收將已完成的任務(wù)作為參數(shù)傳入,可以使用Result屬性訪問(wèn)任務(wù)的返回結(jié)果。
?
??
static void Main(string[] args) { SeeThreadAndTask($"運(yùn)行{nameof(Main)}");
GetStringContinueAsync("張三"); Console.ReadLine(); } /// <summary> ///
使用ContinueWith延續(xù)任務(wù)/// </summary> /// <param name="name"></param> private static
void GetStringContinueAsync(string name) { SeeThreadAndTask($"開(kāi)始
運(yùn)行{nameof(GetStringContinueAsync)}"); var result = GetStringAsync(name);
result.ContinueWith(t=> { string answr = t.Result; Console.WriteLine(answr);
SeeThreadAndTask($"結(jié)束 運(yùn)行{nameof(GetStringContinueAsync)}"); }); }
?
這里我們觀察運(yùn)行結(jié)果可以發(fā)現(xiàn)在執(zhí)行完成任務(wù)后繼續(xù)執(zhí)行ContinueWith方法。其中這個(gè)方法在線程4和任務(wù)2中完成。這里相當(dāng)于又開(kāi)始了一個(gè)新的任務(wù),也就是使用ContinueWith方法對(duì)任務(wù)進(jìn)行一定的延續(xù)。
五、多個(gè)異步方法的使用
在每個(gè)異步方法中可以調(diào)用一個(gè)或多個(gè)異步方法。那么如何進(jìn)行編碼呢?這就看這些異步方法之間是否存在相互依賴(lài)了。
正常來(lái)說(shuō)按照順序調(diào)用:
? ? ? ??
static void Main(string[] args) { SeeThreadAndTask($"運(yùn)行{nameof(Main)}");
ManyAsyncFun(); Console.ReadLine(); }private static async void ManyAsyncFun() {
var result1 = await GetStringAsync("張三"); var result2 = await GetStringAsync("李四
"); Console.WriteLine($"第一個(gè)人是{result1},第二個(gè)人是{result2}"); }
?
?
使用await關(guān)鍵字調(diào)用每個(gè)異步方法。如果一個(gè)異步方法依賴(lài)另一個(gè)異步方法的話(huà),那么這個(gè)await關(guān)鍵字就比較有效,但是如果第二個(gè)異步方法獨(dú)立于第一個(gè)異步方法,這樣可以不使用await關(guān)鍵字,這樣的話(huà)整個(gè)ManyAsyncFun方法將會(huì)更快的返回結(jié)果。
還一種情況,
異步方法不依賴(lài)于其他異步方法,而且不使用await,而是把每個(gè)異步方法的返回結(jié)果賦值給Task比變量,這樣會(huì)運(yùn)行的更快。組合器可以幫助實(shí)現(xiàn)這一點(diǎn),一個(gè)組合器可以接受多個(gè)同一類(lèi)型的參數(shù),并返回同一類(lèi)型的值。如果任務(wù)返回相同的類(lèi)型,那么該類(lèi)型的數(shù)組也可用于接收await返回的結(jié)果。當(dāng)只有等待所有任務(wù)都完成時(shí)才能繼續(xù)完成其他的任務(wù)時(shí),WhenAll方法就有實(shí)際用途,當(dāng)調(diào)用的任務(wù)在等待完成時(shí)任何任務(wù)都能繼續(xù)完成任務(wù)的時(shí)候就可以采用WhenAny方法,它可以使用任務(wù)的結(jié)果繼續(xù)。
? ? ? ?
static void Main(string[] args) { SeeThreadAndTask($"運(yùn)行{nameof(Main)}");
ManyAsyncFunWithWhenAll(); Console.ReadLine(); }private static async void
ManyAsyncFunWithWhenAll() { Task<string> result1 = GetStringAsync("張三"); Task<
string> result2 = GetStringAsync("李四"); await Task.WhenAll(result1, result2);
Console.WriteLine($"第一個(gè)人是{result1.Result},第二個(gè)人是{result2.Result}"); }
?
在使用await依次調(diào)用兩個(gè)異步方法時(shí),診斷會(huì)話(huà)6.646秒,采用WhenAll時(shí),診斷會(huì)話(huà)話(huà)費(fèi)3.912秒,可以看出速度明顯提高了。
???????
?
?
六、使用ValueTasks
C#帶有更靈活的await關(guān)鍵字:它現(xiàn)在可以等待任何提供GetAwaiter方法的對(duì)象。下面我們講一個(gè)可用于等待的新類(lèi)型-----ValueTask,與Task相反,ValueTask是一個(gè)結(jié)構(gòu)。這具有性能優(yōu)勢(shì),因ValueTask在堆上沒(méi)有對(duì)象。
? ? ??
static async Task Main(string[] args) { SeeThreadAndTask($"運(yùn)行{nameof(Main)}");
for (int i = 0; i < 10000; i++) { string result2 = await GetStringDicAsync("張三"
); } Console.WriteLine("結(jié)束"); Console.ReadLine(); } private readonly static
Dictionary<string, string> names = new Dictionary<string, string>(); private
static async ValueTask<string> GetStringDicAsync(string name) { if
(names.TryGetValue(name,out string result)) { return result; } else { result =
await GetStringAsync(name); names.Add(name,result); return result; } }
?
上面事例中我們使用ValueTask替代了Task,因?yàn)槲覀兦懊嬷v,每次使用Task都會(huì)對(duì)內(nèi)存進(jìn)行分配空間,在我們反復(fù)時(shí)會(huì)造成一定的性能上的損耗,但是使用ValueTask只會(huì)存放在Stack中,存放實(shí)際值而不是記憶地址。
七、轉(zhuǎn)換異步模式
并非所有的.NET
Framework的所有的類(lèi)都引用了新的異步方法,在使用框架中不同的類(lèi)的時(shí)候會(huì)發(fā)現(xiàn),還有許多類(lèi)只提供了BeginXXX方法和EndXXX方法的異步模式,沒(méi)有提供基于任務(wù)的異步模式,但是我們可以把異步模式更改為基于任務(wù)的異步模式。
提供的Task.Factory.FromAsync<>泛型方法,將異步模式轉(zhuǎn)換為基于任務(wù)的異步模式。
static void Main(string[] args) { ConvertingAsync(); Console.ReadLine(); }
private static async void ConvertingAsync() { HttpWebRequest request =
WebRequest.Create("http://www.cninnovation.com/") as HttpWebRequest; using
(WebResponse response =await
Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse(null,null
),request.EndGetResponse)) { Stream stream= response.GetResponseStream(); using
(var reader=new StreamReader(stream)) { string content = reader.ReadToEnd();
Console.WriteLine(content.Substring(0,100)); } } }
?
異步編程的錯(cuò)誤處理
上一節(jié)我們講了錯(cuò)誤和異常處理,但是我們?cè)谑褂卯惒椒椒〞r(shí),應(yīng)該知道一些特殊的處理方式,我們先看一個(gè)簡(jiǎn)單的事例
static void Main(string[] args) { Dont(); Console.WriteLine("結(jié)束");
Console.ReadLine(); }static async Task ThrowAfterAsync(int ms, string msg) {
await Task.Delay(ms); throw new Exception(msg); } private static void Dont() {
try { ThrowAfterAsync(200,"第一個(gè)錯(cuò)誤"); } catch (Exception ex) {
Console.WriteLine(ex.Message); } }
?
在這個(gè)事例中,調(diào)用了異步方法,但是并沒(méi)有等待,try/catch就捕獲不到異常,這是因?yàn)镈ont方法在拋出異常前就運(yùn)行結(jié)束了。
一、異步方法的異步處理
那么異步方法的異常怎么處理呢,有一個(gè)較好的方法就是使用await關(guān)鍵字。將其放在try/catch中,異步方法調(diào)用完后,Dont方法就會(huì)釋放線程,但它會(huì)在任務(wù)完成時(shí)保持任務(wù)的引用。
private static async void Dont() { try { await ThrowAfterAsync(200,"第一個(gè)錯(cuò)誤"); }
catch (Exception ex) { Console.WriteLine(ex.Message); } }
?
二、多個(gè)異步方法的異步處理
那么多個(gè)異步方法調(diào)用,每個(gè)都拋出異常怎么處理呢?我們看下面事例中
?
private static async void Dont() { try { await ThrowAfterAsync(200,"第一個(gè)錯(cuò)誤");
await ThrowAfterAsync(100, "第二個(gè)錯(cuò)誤"); } catch (Exception ex) {
Console.WriteLine(ex.Message); } }
?
調(diào)用兩個(gè)異步方法,但是都拋出異常,因?yàn)椴东@了一個(gè)異常之后,try塊代碼就沒(méi)有繼續(xù)調(diào)用第二方法,也就只拋出了第一個(gè)異常
?
private static async void Dont() { try { Task t1 = ThrowAfterAsync(200, "第一個(gè)錯(cuò)誤"
); Task t2= ThrowAfterAsync(100, "第二個(gè)錯(cuò)誤"); await Task.WhenAll(t1,t2); } catch
(Exception ex) { Console.WriteLine(ex.Message); } }
?
對(duì)上述事例修改,采用并行調(diào)用兩個(gè)方法,在2s秒后第一個(gè)拋出異常,1s秒后第二個(gè)異常也拋出了,使用Task.WhenAll,不管是否拋出異常,都會(huì)等兩個(gè)任務(wù)完成。因此就算捕獲了第一個(gè)異常也會(huì)執(zhí)行第二個(gè)方法。但是我們只能看見(jiàn)拋出的第一個(gè)異常,沒(méi)有顯示第二個(gè)異常,但是它存在在列表中。
三、使用AggregateException
這里為了得到所有失敗任務(wù)的異常信息,看將Task.WhenAll返回的結(jié)果寫(xiě)到一個(gè)Task變量中。這個(gè)任務(wù)會(huì)一個(gè)等到所有任務(wù)結(jié)束。
private static async void Dont() { Task taskResult = null; try { Task t1 =
ThrowAfterAsync(200, "第一個(gè)錯(cuò)誤"); Task t2 = ThrowAfterAsync(100, "第二個(gè)錯(cuò)誤"); await
(taskResult=Task.WhenAll(t1,t2)); } catch (Exception ex) {
Console.WriteLine(ex.Message);foreach (var item in
taskResult.Exception.InnerExceptions) { Console.WriteLine(item.Message); } } }
?
?
這里可以訪問(wèn)外部任務(wù)的Exception屬性了。Exception屬性是AggregateException類(lèi)型的。這里使用Task.Exception.InnerExceptions屬性,它包含了等待中所有的異常列表。這樣就可以輕松的變量所有的異常了。
總結(jié)
本篇文章介紹了三種不同的異步模式,同時(shí)也介紹
了相關(guān)的異步編程基礎(chǔ)。如何對(duì)應(yīng)的去使用異步方法大有學(xué)問(wèn),用的好的異步編程減少性能消耗,提高運(yùn)行效率。但是使用不好的異步編程提高性能消耗,降低運(yùn)行效率也不是不可能的。這里也只是簡(jiǎn)單的介紹了異步編程的相關(guān)基礎(chǔ)知識(shí)以及錯(cuò)誤處理。更深更完美的編程模式還得實(shí)踐中去探索。異步編程使用async和await關(guān)鍵字等待這些方法。而不會(huì)阻塞線程。異步編程的介紹到這里就暫時(shí)結(jié)束,下一篇文章我們將詳細(xì)介紹反射、元數(shù)據(jù)和動(dòng)態(tài)編程。
?
?
不是井里沒(méi)有水,而是你挖的不夠深。不是成功來(lái)得慢,而是你努力的不夠多。
?
?
? c#基礎(chǔ)知識(shí)詳解系列 <https://www.cnblogs.com/hulizhong/p/11205119.html>
?
歡迎大家掃描下方二維碼,和我一起學(xué)習(xí)更多的C#知識(shí)
?
熱門(mén)工具 換一換
