您好,登錄后才能下訂單哦!
Task類在.NET4.5中,做了一些改進(jìn),比如新增了方法ConfigureAwait,Delay,Run等方法。其中一個(gè)重要修改,就是對(duì)于異常的處理。
在.NET4.0中,Task中拋出的異常,如果沒有去捕獲,在Task被垃圾回收的時(shí)候,析構(gòu)函數(shù)檢測(cè)到該Task對(duì)象還有未被處理過的異常,會(huì)拋出這個(gè)異常,并且導(dǎo)致進(jìn)程終結(jié),進(jìn)程終結(jié)的時(shí)間是由垃圾回收器和析構(gòu)方法決定的。(可以通過注冊(cè)TaskSchedular.UnobservedTaskException事件處理未捕獲的異常。)
ThrowUnobservedTaskExceptions節(jié)點(diǎn)
在.NET4.5中,微軟改變了策略,對(duì)于task中未處理的異常,默認(rèn)情況下不會(huì)導(dǎo)致殺死進(jìn)程。這個(gè)可以通過配置ThrowUnobservedTaskExceptions節(jié)點(diǎn)實(shí)現(xiàn)。
默認(rèn)情況下,ThrowUnobservedTaskExceptions這個(gè)節(jié)點(diǎn)的enabled=false。如下配置。
<configuration> <runtime> <ThrowUnobservedTaskExceptions enabled="false"/> </runtime> </configuration>
這種情況下,在.NET4.5中,GC回收對(duì)象的時(shí)候,是不會(huì)導(dǎo)致程序崩潰的,未捕獲的異常就這樣消失了。比如下面的代碼:
static void Main(string[] args) { for (int i = 0; i < 10; i++) { var t = Task.Factory.StartNew<int>(() => { throw new Exception("xxxxxx"); return 1; } , CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); } while (true) { GC.Collect(); Thread.Sleep(1000); } Console.ReadKey(); }
但是如果設(shè)置<ThrowUnobservedTaskExceptions enabled="true"/>,那么程序就會(huì)崩潰,這和在.NET4.0中的結(jié)局一樣。
當(dāng)然,對(duì)于所有的異常,都是建議捕獲并且處理的。.NET4.0和4.5都提供了TaskScheduler.UnobservedTaskException事件,通過監(jiān)聽這個(gè)事件,也可以捕獲到這個(gè)異常。
代碼如下:
TaskScheduler.UnobservedTaskException += (o, ev) => { Console.WriteLine(ev.Exception); ev.SetObserved(); Console.WriteLine("---------"); };
注意:ev.SetObserved();方法必須要調(diào)用,這樣才能阻止進(jìn)程崩潰。
.NET4.0中Task的異常
還有一處改進(jìn)是對(duì)于異常的捕獲上。對(duì)于Task中拋出的異常,外部的try catch是無法捕獲的。例如:
static Task<int> f() { try { var t = Task.Factory.StartNew<int>(() => { throw new Exception("xxxxxx"); return 1; }); return t; } catch { Console.WriteLine("error"); } return null; }
無論是在.net4.0還是.net4.5,上述代碼都是不會(huì)走到catch中的。只有當(dāng)運(yùn)行Task.Wait或者讀取Task.Result的時(shí)候(這些方法都會(huì)引起阻塞),才會(huì)拋出異常,由于運(yùn)行的Task可能是包含了多個(gè)子Task,或者在WaitAll多個(gè)Task,那么異常可能會(huì)出現(xiàn)多個(gè)。Task類會(huì)把這些異常包成AggregateException異常。要獲得異常的正真信息,需要訪問AggregateException.InnerExceptions屬性。如下代碼:
Task<int> t1 = Task.Factory.StartNew<int>(() => { throw new Exception("error1"); }); Task<int> t2 = Task.Factory.StartNew<int>(() => { throw new Exception("error2"); }); Task<int> t3 = Task.Factory.StartNew<int>(() => { throw new Exception("error3"); }); try { Task.WaitAll(t1,t2,t3); } catch (AggregateException ex) { Console.WriteLine("Exception Type:{0}", ex.GetType()); Console.WriteLine("Exception Message:{0}", ex.Message); Console.WriteLine("Exception StackTrace:{0}", ex.StackTrace); Console.WriteLine("Exception InnerException.Message:{0}", ex.InnerException.Message); foreach (var innerEx in ex.InnerExceptions) { Console.WriteLine("InnerExceptions.Message:{0}", innerEx.Message); } }
Task.WaitAll三個(gè)Task,捕獲異常的時(shí)候,類型為AggregateException,并且可以通過InnerExceptions,遍歷出每一個(gè)異常。這里值得注意的是,如果AggregateException的InnerExceptions有3個(gè)異常的話,AggregateException的InnerException會(huì)拋出哪個(gè)異常?根據(jù)老趙的說法,C#開發(fā)團(tuán)隊(duì)“故意”不提供文檔說明究竟會(huì)拋出哪個(gè)異常。因?yàn)樗麄儾⒉幌胱龀鲞@方面的約束,因?yàn)檫@部分行為一旦寫入文檔,便成為一個(gè)規(guī)定和限制,為了類庫的兼容性今后也無法對(duì)此做出修改。
上面代碼的輸出如下:
async/await
在.net 4.5中,c#5.0的語法支持了async/await的寫法,這種寫法下,可以按照同步的思路寫異步方法。而且,異常處理也變得可以直接捕獲,在await的時(shí)候,代碼可以直接捕獲異常,例如下面的代碼。
static void Main(string[] args) { var t = f(); Console.ReadKey(); } async static Task<int> f() { int val = 0; try { val = await Task.Factory.StartNew<int>(() => { throw new NotSupportedException("xxxxxx"); return 1; }); } catch (Exception ex) { Console.WriteLine("Exception Type:{0}", ex.GetType()); Console.WriteLine("Exception Message:{0}", ex.Message); Console.WriteLine("Exception StackTrace:{0}", ex.StackTrace); } return val; }
輸出如下:
注意,可以看到錯(cuò)誤的堆棧信息。從System.Threading.Tasks.Task類切換到了 System.Runtime.CompilerServices.TaskAwaiter。
C# 使用了SynchronizationContext類完成了這個(gè)切換。當(dāng)await一個(gè)Task的時(shí)候,當(dāng)前的 SynchronizationContext對(duì)象被存儲(chǔ)下來。當(dāng)方法繼續(xù)向下運(yùn)行的時(shí)候,await關(guān)鍵字的結(jié)構(gòu)使用Post方法,在之前保存的SynchronizationContext類的基礎(chǔ)上,繼續(xù)運(yùn)行該方法。更多細(xì)節(jié)不在這里描述。因此只要await關(guān)鍵字的方法上出現(xiàn)了異常,就可以捕獲掉,并且捕獲的異常類型,就可以不把異常往上層拋,還有一個(gè)不同點(diǎn)是await方法捕獲的異常類型就是直接的類型,而不是System.AggregateException。
但是如果在await等待是多個(gè)Task,并且這多個(gè)Task都拋出了異常,那么,最終捕獲的異常也會(huì)是System.AggregateException類型。并且也可以遍歷出所有的異常。和之前的處理同步情況下是一樣的。
static void Main(string[] args) { var t = f(); Console.WriteLine(t.Result); Console.ReadKey(); } async static Task<int> f() { int val = 0; Task<int[]> all = null; try { Task<int> t1 = Task.Factory.StartNew<int>(() => { throw new NotImplementedException("error1"); return 1; }); Task<int> t2 = Task.Factory.StartNew<int>(() => { throw new NotImplementedException("error2"); return 2; }); Task<int> t3 = Task.Factory.StartNew<int>(() => { throw new NotImplementedException("error3"); return 3; }); await (all = Task.WhenAll(t1, t2, t3)); val = all.Result.Sum(); } catch (Exception ex) { Console.WriteLine("Exception Type:{0}", ex.GetType()); Console.WriteLine("Exception Message:{0}", ex.Message); Console.WriteLine("Exception StackTrace:{0}", ex.StackTrace); foreach (var innerEx in all.Exception.InnerExceptions) { Console.WriteLine("InnerExceptions.Message:{0}", innerEx.Message); } } return val; }
輸出如下:
上述代碼中,使用了WhenAll方法,返回的是一個(gè)Task類。由于t1,t2,t3中都包含了異常,因此返回的Task中有3個(gè)異常,但await關(guān)鍵字只會(huì)允許拋出一個(gè)具體的異常,因此,此處拋出了第一個(gè)異常。通過對(duì)all變量的遍歷,可以得到所有的異常。
WhenAll 方法
WhenAll方法是.NET4.5中新增的方法。它的返回值是一個(gè)Task,僅當(dāng)所有的Task都完成的時(shí)候,返回的這個(gè)Task才算完成,并且返回值可以是各個(gè)task返回值的一個(gè)數(shù)組。
因此可以把WhenAll方法看成是一組Task的合并。WhenAll和WaitAll有點(diǎn)類似,但本質(zhì)上很多不同。
1.調(diào)用Task.WaitAll的時(shí)候,會(huì)阻塞當(dāng)前線程,直到所有的Task都完成了。而Task.WhenAll方法不會(huì)阻塞當(dāng)前線程,而是直接返回了一個(gè)Task,只有在讀取這個(gè)Task的Result的時(shí)候,才會(huì)引起阻塞。
2.WaitAll的各類重載方法,它們的返回值是void或者bool,而WhenAll的返回值是Task。因此WhenAll方法更好的支持async/await異步寫法。
下面代碼演示W(wǎng)henAll方法:
static void Main(string[] args) { Task<int> t1 = Task.Factory.StartNew<int>(() => { Thread.Sleep(1000); return 1; }); Task<int> t2 = Task.Factory.StartNew<int>(() => { Thread.Sleep(2000); return 2; }); Task<int> t3 = Task.Factory.StartNew<int>(() => { Thread.Sleep(3000); return 3; }); var all = Task.WhenAll(t1, t2, t3); while (true) { Console.WriteLine("{0} IsCompleted:{1}", DateTime.Now.ToString("HH:mm:ss fff"), all.IsCompleted); Thread.Sleep(200); if (all.IsCompleted) break; } var c = all.Result; Console.WriteLine(c.Sum()); }
輸出的結(jié)果為:
ConfigureAwait方法
ConfigureAwait方法的作用是指定代碼在執(zhí)行await操作的時(shí)候,是否捕獲上下文,捕獲上下文會(huì)帶來性能開銷。不捕獲上下文,在ASP.NET或者GUI程序時(shí),可能帶來問題。更多的細(xì)節(jié)可以參考Stephen Cleary 的異步編程中的最佳做法
總之,.NET4.5中的Task的一些改進(jìn),都是為了迎合異步async/await的寫法而做出的改進(jìn)。更多參考資料:
http://msdn.microsoft.com/zh-cn/magazine/jj991977.aspx
http://msdn.microsoft.com/zh-cn/magazine/gg598924.aspx
以及Stephen Cleary的blog:http://blog.stephencleary.com/
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。