雖然今天的重點是.NET4.5的async/await設計模式可是因爲不少人對於.NET4.0中的Task仍然仍是沒有接觸過,Task也是.NET 4.5 async await的基礎概念之一,值得你們花點時間熟悉,那麼這裏就將他也做爲一個附加專題來作一下講解。 web
到咱們在開發SignalR程序的時候,就必需要使用到多線程,假設沒有.NET4.5的支持,那麼你可能想到的最簡單方式就是使用Task,它取代了傳統的Thread,TheadPool的寫法,能大幅度的簡化同步邏輯的寫法,頗爲便利,下面咱們來看幾個典型的範例。 設計模式
Test1()用以另外一Thread執行Thread.Sleep()及Console.WriteLine(),效果與ThreadPool.QueueUserWorkItem()至關。多線程
private static void Test1() { //Task能夠代替TheadPool.QueueUserWorkItem使用 Task.Factory.StartNew(() => { Thread.Sleep(2000); Console.WriteLine("Done!"); }); Console.WriteLine("Async Run..."); }
StartNew()完會馬上執行下一行,故會先看到Aync Run,1秒後打印出Done。架構
同時啓動多個做業多工並行(多線程並行),但要等待各做業完成再繼續下一步的應用場境傳統方式上可經過WaitHandle、AutoResetEvent、ManualResetEvent等機制實現;Task的寫法至關簡單,創建多個Task對象,再做爲Task.WaitAny()或Task.WaitAll()的參數就搞定了! 異步
private static void Test2() { var task1 = Task.Factory.StartNew(() => { Thread.Sleep(3000); Console.WriteLine("Done!(3s)"); }); var task2 = Task.Factory.StartNew(() => { Thread.Sleep(5000); Console.WriteLine("Done!(5s)"); }); //等待任意做業完成後繼續 Task.WaitAny(task1, task1); Console.WriteLine("WaitAny Passed"); //等待全部做業完成後繼續 Task.WaitAll(task1, task2); Console.WriteLine("WaitAll Passed"); }
task1耗時3秒、task2耗時5秒,因此3秒後WaitAny()執行完成、5秒後WaitAll()執行完畢。async
經過StartNew<T>()指定傳回類型創建做業,隨後以Task.Result取值,不用額外Code就能確保多工做業執行完成後纔讀取結果繼續運行 測試
private static void Test3() { var task = Task.Factory.StartNew<string>(() => { Thread.Sleep(2000); return "Done!"; }); //使用秒錶計時 Stopwatch sw = new Stopwatch(); sw.Start(); //讀取task.Result時,會等到做業完成傳回值後才繼續 Console.WriteLine("{0}", task.Result); sw.Stop(); //取得task.Result耗時約2秒 Console.WriteLine("Duration: {0:N0}ms", sw.ElapsedMilliseconds); }
實際執行,要花兩秒才能跑完Console.WriteLine("{0}", task.Result),其長度就是Task執行並回傳結果的時間。 大數據
private static void Test4() { Task.Factory.StartNew(() => { Thread.Sleep(1000); Console.WriteLine("Done!"); }).ContinueWith(task => { //ContinueWith會等待前面的任務完成才繼續 Console.WriteLine("In ContinueWith"); }); Console.WriteLine("Async Run..."); }
如預期,ContinueWith()裏的程序會在Task完成後才被執行。
.ContinueWith()傳回值還是Task對象,因此咱們能夠跟jQuery同樣連連看,在ContinueWith()後方再接上另外一個ContinueWith(),各段邏輯便會依順序執行。 this
static void test5() { //ContinueWith()能夠串接 Task.Factory.StartNew(() => { Thread.Sleep(2000); Console.WriteLine("{0:mm:ss}-Done", DateTime.Now); }) .ContinueWith(task => { Console.WriteLine("{0:mm:ss}-ContinueWith 1", DateTime.Now); Thread.Sleep(2000); }) .ContinueWith(task => { Console.WriteLine("{0:mm:ss}-ContinueWith 2", DateTime.Now); }); Console.WriteLine("{0:mm:ss}-Async Run...", DateTime.Now); }
Task耗時兩秒,第一個ContinueWith()耗時2秒,最後一個ContinueWith()繼續在4秒後執行。 spa
ContinueWith()中的Action<Task>都會有一個輸入參數,用於以得知前一Task的執行狀態,有IsCompleted, IsCanceled, IsFaulted幾個屬性可用。要取消執行,得藉助CancellationTokenSource及其所屬CancellationToken類型,作法是在Task中持續呼叫CancellationToken.ThrowIfCancellationRequested(),一旦外部呼叫CancellationTokenSource.Cancel(),便會觸發OperationCanceledException,Task有監控此異常情況的機制,將結束做業執行後續ContinueWith(),並指定Task.IsCanceled爲True;而當Task程序發送Exception,也會結束觸發ContinueWith (),此時Task.IsFaulted爲True,ContinueWith()中可經過Task.Exception.InnerExceptions取得錯誤細節。如下程序同時可測試Task正常、取消及錯誤三種情景,使用者經過輸入1,2或3來決定要測試哪種。在Task外先聲明一個CancellationTokenSource類型,將其中的Token屬性當成StartNew()的第二項參數,而Task中則保留最初的五秒能夠取消,方法是每隔一秒呼叫一次CancellationToken.ThrowIfCancellationRequested(),當程序外部調用CancellationTokenSource.Cancel(),Task就會結束。5秒後若未取消,再依使用者決定的測試情境return結果或是拋出Exception。ContinueWith()則會檢查IsCanceled, IsFaulted等標識,並輸出結果。
private static void Test6() { CancellationTokenSource cts = new CancellationTokenSource(); CancellationToken cancelToken = cts.Token;//獲取與此CancellationTokenSource關聯的CancellationToken Console.Write("Test Option 1, 2 or 3 (1-Complete / 2-Cancel / 3-Fault) : "); var key = Console.ReadKey(); Console.WriteLine(); Task.Factory.StartNew<string>(() => { //保留5秒檢測是否要Cancel for (var i = 0; i < 5; i++) { Thread.Sleep(1000); //如cancelToken.IsCancellationRequested //引起OperationCanceledException cancelToken.ThrowIfCancellationRequested(); } switch (key.Key) { case ConsoleKey.D1: //選1時 return "OK"; case ConsoleKey.D3: //選3時 throw new ApplicationException("MyFaultException"); } return "Unknown Input"; }, cancelToken).ContinueWith(task => { Console.WriteLine("IsCompleted: {0} IsCanceled: {1} IsFaulted: {2}", task.IsCompleted, task.IsCanceled, task.IsFaulted); if (task.IsCanceled) { Console.WriteLine("Canceled!"); } else if (task.IsFaulted) { Console.WriteLine("Faulted!"); foreach (Exception e in task.Exception.InnerExceptions) { Console.WriteLine("Error: {0}", e.Message); } } else if (task.IsCompleted) { Console.WriteLine("Completed! Result={0}", task.Result); } }); Console.WriteLine("Async Run..."); //若是要測Cancel,2秒後觸發CancellationTokenSource.Cancel if (key.Key == ConsoleKey.D2) { Thread.Sleep(2000); cts.Cancel(); } }
Task能作的事,過去使用Thread/ThreadPool配合Event、WaitHandle同樣能辦到,但使用Task能以比較簡潔的語法完成相同工做,使用.NET 4.0開發多線程時可多加利用。
到這裏,咱們繼續回到本來的.NET4.5中,首先咱們設計幾種異步做業新舊寫法法進行對比
private static void DownLoadWebPageSourceCode_Old() { WebClient wc = new WebClient(); wc.DownloadStringCompleted += CompletedHandler; wc.DownloadStringAsync(new Uri("http://www.cnblogs.com/rohelm")); while (wc.IsBusy) { Console.WriteLine("還沒下完,我喝一回茶!"); } } private static void CompletedHandler(object sender, DownloadStringCompletedEventArgs e) { Console.WriteLine(e.Result); }
運行效果以下:
private static async void DownLoadWebPageSourceCode_New() { WebClient wc = new WebClient(); Console.WriteLine(await wc.DownloadStringTaskAsync("http://www.cnblogs.com/rohelm")); }
而它的內部實現機制其實是咱們前面的附加專題中提到的Task,咱們來查看下這個方法的源碼:
[ComVisible(false), HostProtection(SecurityAction.LinkDemand, ExternalThreading=true)] public Task<string> DownloadStringTaskAsync(string address) { return this.DownloadStringTaskAsync(this.GetUri(address)); } [ComVisible(false), HostProtection(SecurityAction.LinkDemand, ExternalThreading=true)] public Task<string> DownloadStringTaskAsync(Uri address) { TaskCompletionSource<string> tcs = new TaskCompletionSource<string>(address); DownloadStringCompletedEventHandler handler = null; handler = delegate (object sender, DownloadStringCompletedEventArgs e) { this.HandleCompletion<DownloadStringCompletedEventArgs, DownloadStringCompletedEventHandler, string>(tcs, e, args => args.Result, handler, delegate (WebClient webClient, DownloadStringCompletedEventHandler completion) { webClient.DownloadStringCompleted -= completion; }); }; this.DownloadStringCompleted += handler; try { this.DownloadStringAsync(address, tcs); } catch { this.DownloadStringCompleted -= handler; throw; } return tcs.Task; }
因爲上面咱們已經說過它的內部本質仍是Task因此它的,取消該非同步做業依舊藉助CancellationTokenSource及其所屬CancellationToken類型
private static async Task TryTask() { CancellationTokenSource cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(1)); Task<string> task = Task.Run(() => PirntWords("Hello,Wrold!", cts.Token), cts.Token); Console.WriteLine(task.Result); await task; } private static string PirntWords(string input, CancellationToken token) { for (int i = 0; i < 20000000; i++) { Console.WriteLine(input); token.ThrowIfCancellationRequested(); } return input; }
public async Task<ActionResult> DoAsync() { ServiceClient1 client1 = new ServiceClient1(); ServiceClient2 client2 = new ServiceClient2(); var task1 = client1.GetDataAsync(); var task2 = client2.GetDataAsync(); await Task.WhenAll(task1,task2); return View("View名稱",new DataModel(task1.Result,task2.Rusult)); }
是否是發現很是的方便,實用啊!
未完待續....
後續內容:
對WebAPI和WCF的進行一個簡單比較,探討WebAPI的機制,功能,架構,WinFrom Client/WebService Client大數據上傳...備註:本文章版權的沒有,歸.NET使用者共有。