目錄html
如今的.net異步編程中,通常都是使用 基於任務異步模式(Task-based Asynchronous Pattern,TAP)實現異步編程git
可參考微軟文檔:使用基於任務的異步模式github
基於任務的異步模式,是使用System.Threading.Tasks.Task
命名空間中的System.Threading.Tasks.Task<TResult>
和 System.Threading.Tasks
類型實現異步編程,編程
配合着C#5.0(.net 4.5)中添加的兩個關於異步編程的關鍵字async
和 await
,能夠快速的實現異步操做。數組
在C#5.0(.net 4.5)中添加了兩個關於異步編程的關鍵字async
和 await
服務器
兩個關鍵字能夠快速的建立和使用異步方法。async
和await
關鍵字只是編譯器功能,編譯後會用Task類建立代碼,實現異步編程。其實只是C#對異步編程的語法糖,本質上是對Task對象的包裝操做。多線程
因此,若是不使用這兩個關鍵字,也能夠用C#4.0中Task類的方法來實現一樣的功能,只是沒有那麼方便。(見:.NET異步程序設計之任務並行庫)mvc
async
:用於異步方法的函數名前作修飾符,處於返回類型左側。async只是一個標識符,只是說明該方法中有一個或多個await表達式,async自己並不建立異步操做。異步
await
:用於異步方法內部,用於指明須要異步執行的操做(稱之爲await表達式),注意一個異步方法中能夠有多個await表達式,並且應該至少有一個(如果沒有的話編譯的時候會警告,但仍是能夠構建和執行,只不過就至關於同步方法而已)。async
其實這裏你有沒有想起Task類中爲了實現延續任務而使用的等待者,好比:使用task.GetAwaiter()
方法爲task類建立一個等待者(能夠參考;3.3.2使用Awaiter)。await
關鍵字就是基於 .net 4.5中的awaiter對象實現的。
總而言之,如果有await
表達式則函數必定要用async
修飾,如果使用了async
修飾的方法中沒有await
表達式,編譯的時候會警告!
使用async
和await
定義的異步方法的返回值只有三種:Task<T>
、Task
、void
異步方法中有return語句,則返回值類型爲Task<T>
,其中T
是return語句返回對象的類型。(編譯器幫咱們把T
類型數據轉換爲Task<T>
類型)。因此不要使用return返回一個Task<T>
類型的對象,而是隻須要返回一個T
類型數據便可
異步方法中沒有return語句,可是你須要查看異步方法的狀態或是要對Task對象操做(好比task.Wait()),則可定義返回值類型爲Task
。
異步方法中沒有return語句且不須要查看異步方法的狀態,則可認爲是返回值是void
類型。此時稱之爲「調用並忘記」(fire and forget),其實這並非一個很好的用法(具體的分析能夠看:異步方法的異常處理)!
如果真的須要返回一個Task<T>
類型的數據 (好比在非async修飾的方法中,定義返回值類型就是一個Task<T>
類型),則:
return Task.Run(()=>{……})
將T
類型轉換爲Task<T>
類型:return Task.FromResult(T data)
將IAsyncResult
類型對象轉換爲Task類型:return Task.Factory.FromTask(IAsyncResult data)
async
修飾符只能用於返回Task
、Task<T>
和viod
的方法,或者Lambda表達式中。
謝謝@coredx提醒:C#7.1中應用程序的入口點能夠具備async修飾符,參考:What's new in C# 7.1async
不能用於程序的入口點,即Main()不能使用async
修飾符。
簡單的說:函數體中含有await
表達式的函數,必須使用async
修飾!
而一個使用了async
修飾的方法,在調用它的時候若有必要則必須使用await
等待!
使用了await
等待的方法,則其調用方法又必須使用async
修飾,這從而造成了一個循環,這就是async傳染
換句話說就是哪裏調用了async
修飾的方法則async
就會傳染到哪!
能夠有的時候咱們並不想要咱們的方法變爲async
修飾的方法,因此須要避免async
傳染
避免的主要方法就是使用延續任務來避免,你想想以前在Task類中,使用延續任務時,主要就是避免使用await
等待!
以前咱們說了:主函數Main()不能使用(C#7.1Main能夠是異步的),而調用異步方法的那個同步方法,稱之爲病源隔斷方法,由於在這裏開始,再也不會發生async傳染。async
修飾符,由於Main函數不可以是異步方法,這也就意味着一切的異步方法最終的調用方法必定是同步方法
示例1:定義一個異步方法,並調用它
static void Main(string[] args) { Task<int> t = SumAsync(1, 2); t.ContinueWith(t => Console.WriteLine( "異步操做結果爲:" + t.Result)); for (int i = 0; i < 10; i++) { Thread.Sleep(1000); Console.WriteLine($"循環次數{i}"); } Console.ReadKey(); } private static async Task<int> SumAsync(int num1, int num2) { int sum = await Task.Run(() => { Thread.Sleep(3000); return num1 + num2; }); return sum; }
示例說明 :
異步方法SumAsync()函數體中返回的是整型sum,即返回的是一個整型,可是在聲明函數時,返回值需寫爲:Task<int>
反過來講:如果異步方法的返回值類型爲Task<T>
,則在方法中只須要返回T
類型的數據。
這一點就是和Task.Run()的返回值的書寫方式同樣,即若Task.Run()
參數是有返回值的委託Func<TResult>
,則Task.Run()
返回值是Task<TResult>
泛型
異步方法的命名默認是在最後加"Async",即"XXXAsync"。
調用異步方法的方法稱之爲調用方法(在這裏就是主函數Main()),調用方法和被調用的異步方法,不必定在不一樣的線程中。
其實你看上面示例的代碼也會發現,單獨把異步操做封裝爲一個異步方法,這樣能夠爲異步操做傳參!
你能夠還記得的在.net異步程序設計之任務並行庫中,屢次說明Task.Run()的參數只能是無參委託。
有一點在這裏說明一下:關於異步匿名方法,或是異步Lambda表達式。
在爲一個事件綁定事件處理程序的時候,對於一些簡單的事件處理程序,咱們可使用Lambda表達式
可是咱們想要異步操做Lambda表達式,則能夠直接寫爲:
Butten.Click+=async(sender,e)=>{...await...}
詳細能夠參照《C#圖解教程:20.5使用異步Lambda表達式》
依舊是上面的示例,咱們在每一個操做中、操做前、操做後都打印其當前所處的線程,仔細的觀察,異步方法的執行順序。
再次強調,這裏用async修飾的方法,稱之爲異步方法,這裏調用該異步方法的方法,稱之爲調用方法
代碼:
//調用方法 static void Main(string[] args) { Console.WriteLine($"-1-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------調用方法中調用異步方法以前的代碼"); Task<int> result = SumAsync(1, 2); Console.WriteLine($"-3-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------調用方法中調用異步方法以後的代碼"); result.ContinueWith(t => Console.WriteLine($"-8-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------這是延續任務的線程" + "-異步操做結果爲:" + result.Result)); Console.WriteLine($"-4-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------調用方法中延續任務以後的代碼"); for (int i = 0; i < 5; i++) { Thread.Sleep(1000); Console.WriteLine($"正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}"+$"循環次數{i}--------------------------------------調用方法中延續任務以後的代碼"); } Console.ReadKey(); } //異步方法 private static async Task<int> SumAsync(int num1, int num2) { Console.WriteLine($"-2-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------異步方法中await表達式以前的代碼"); int sum1 = await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"-5-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------這是第一個await表達式的線程"); return num1 + num2; }); Console.WriteLine($"-6-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------異步方法中await表達式以後的代碼"); int sum2=await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"-7-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------這是第二個await表達式的線程"); return num1 + num2; }); return sum1+sum2; }
運行結果:
說明:
注意運行順序:
調用異步方法方法後,按照同步順序執行異步方法中await表達式以前的代碼,
當運行到第1個await表達式後,建立一個新的線程,後臺執行該await表達式,實現異步。
第1個await表達式,未完成以前,繼續執行調用函數中的異步方法以後的代碼(注意await表達式後臺未完成以前,不是繼續執行await表達式以後的代碼,而是繼續執行調用函數中的異步方法以後的代碼),
當第1個await表達式在後臺完成後,繼續執行異步方法中第1個await表達式以後的代碼,
當運行到第2個await表達式後,建立一個新的線程,後臺運行該await表達式,
第2個await表達式,未完成以前,繼續執行調用函數中被第1個await完成後打斷的的代碼
當第2個await表達式在後臺運行完成後,繼續執行異步方法中第2個await表達式以後的代碼,
當異步方法運行到return後,則開始調用方法中的對該異步方法的延續任務
該延續任務和調用方法不在一個線程中,這裏有可能和第2個await表達式在同一個線程中,也有可能和第1個await表達式在同一個線程中。
具體可參考:.net異步編程值任務並行庫-3.6取消異步操做
原理是同樣的,都是使用CancellationToken
和CancellationTokenSource
兩個類實現取消異步操做
看一個簡單的示例:
static void Main(string[] args) { CancellationTokenSource cts = new CancellationTokenSource();//生成一個CancellationTokenSource對象, CancellationToken ct = cts.Token;//該對象能夠建立CancellationToken對象,即一個令牌(token) Task result = DoAsync(ct, 50); for (int i = 0; i <= 5; i++)//主線程中的循環(模擬在異步方法聲明以後的工做) { Thread.Sleep(1000); Console.WriteLine("主線程中的循環次數:" + i); } cts.Cancel();//注意在主線程中的循環結束後(5s左右),運行到此處, //則此時CancellationTokenSource對象中的token.IsCancellationRequested==true //則在異步操做DoAsync()中根據此判斷,則取消異步操做 Console.ReadKey(); CancellTask(); CancellTask2(); } //異步方法:取消異步操做的令牌經過參數傳入 static async Task DoAsync(CancellationToken ct, int Max) { await Task.Run(() => { for (int i = 0; i <= Max; i++) { if (ct.IsCancellationRequested)//一旦CancellationToken對象的源CancellationTokenSource運行了Cancel();此時CancellationToken.IsCancellationRequested==ture { return; } Thread.Sleep(1000); Console.WriteLine("次線程中的循環次數:" + i); } }/*,ct*/);//這裏取消令牌能夠做爲Task.Run()的第二個參數,也能夠不寫! }
「調用方法能夠調用任意多個異步方法並接收它們返回的Task對象。而後你的代碼會繼續執行其餘任務,但在某個點上可能會須要等待某個特殊Task對象完成,而後再繼續。爲此,Task類提供了一個實例方法wait,能夠在Task對象上調用該方法。」--《C#圖解教程》
Wait
方法用於單一Task的對象。如果想要等待多個Task,可使用Task類中的兩個靜態方法,其中WaitAll
等待全部任務都結束,WaitAny
等待任一任務結束。
示例:使用Task.WaitAll()和Task.WaitAny()
static void Main(string[] args) { Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2 }:Task以前..."); Task<int> t1 = DoAsync(2000); Task<int> t2 = DoAsync(6000); //Task.WaitAll(t1, t2);//等待t1和t2都完畢後才進行後續的代碼(即阻塞了主線程) //Task.WaitAny(t1, t2);//等待t1和t2中有任一個完成(調試的時候,你就會發現當t1完成後就開始執行後續的循代碼) for (int i = 0; i < 10; i++) { Thread.Sleep(1000); Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2}:循環中"); } Console.ReadKey(); } private static async Task<int> DoAsync(int num) { int result = await Task.Run(() => { Thread.Sleep(num); Console.WriteLine($"當前線程ID{Thread.CurrentThread.ManagedThreadId,2}:異步操做之等待:{num}s"); return num; }); return result; }
說明1 :
對代碼中註釋掉的代碼分別調試,則能夠發現其中的不一樣。
Task.WaitAll(params Task[] tasks)
:表示中止當前主線程,等待Task類型數組tasks中的全部Task操做結束,即會發生阻塞
Task.WaitAll()調試結果:
說明2:
Task.WaitAny(params Task[] tasks)
:表示中止當前主線程,等待Task類型數組tasks中的任一個Task操做結束,也是會發生阻塞,單是阻塞主線程在任一個Task操做結束,以後主線程會繼續,其餘的Task在後臺繼續
Task.WaitAny()調試結果:
其實在一個方法中調用多個異步方法時候,當某個異步方法依賴於另一個異步方法的結果的時候,咱們通常是在每個調用的異步方法處使用await
關鍵字等待該異步操做的結果,可是這樣就會出現async
傳染。
await
不一樣於Wait()
,await
等待是異步的,不會阻塞線程,而Wait()
會阻塞線程
注意如無必用,或是不存在對某個異步操做的等待,儘可能不要使用await
,直接把異步操做的返回值給Task<T>
類型的變量,可使程序運行的更快!
其實你也注意到了:不使用await
等待異步操做,則異步操做的返回值就是定義的返回值Task<T>
,可是使用await
等待則異步操做的返回值就是具體的簡單類型,好比int類型等。
換言之:異步方法的返回值是Task<T>
,則使用await
等待能夠直接獲取異步方法的T
類型的返回值
示例:
static void Main(string[] args) { ReferencingMethodAsync(); } //該調用函數也要使用async關鍵字修飾(即async傳染),由於使用了await等待, private static async void ReferencingMethodAsync() { int result1 = await SumAsync(1, 2);//這裏使用了await 關鍵字則,調用方法MultipleMethod2()必須使用async修飾(即async傳染性) int result2 = await SumAsync(1, result1); Console.WriteLine(result2); } private static async Task<int> SumAsync(int num1, int num2) { int sum = await Task.Run(() => { Thread.Sleep(3000); return num1 + num2; }); return sum; }
Task.WhenAll()
和Task.WhenAny()
是Task.WaitAll()
和Task.WaitAny()
的異步版本,即異步等待Task完成
示例:使用Task.WhenAll()和Task.WhenAny()
static void Main(string[] args) { Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2 }:Task以前..."); Task<int> t1 = DoAsync(2000); Task<int> t2 = DoAsync(6000); //Task.WhenAll(t1, t2);//異步等待t1和t2兩個完成(調試的時候你會發現任務t1和t2都在新的線程中執行,主線繼續執行後續的循環代碼) //Task.WhenAny(t1, t2);//異步等待t1和t2中任一個完成(調試的時候你就會發現兩個任務分別在新線程中執行,線程繼續執行後續的循環代碼,當t1完成後,繼續後續的循環代碼) for (int i = 0; i < 10; i++) { Thread.Sleep(1000); Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2}:循環中"); } Console.ReadKey(); } private static async Task<int> DoAsync(int num) { int result = await Task.Run(() => { Thread.Sleep(num); Console.WriteLine($"當前線程ID{Thread.CurrentThread.ManagedThreadId,2}:異步操做之等待:{num}s"); return num; }); return result; }
說明1:
在示例中看到Task.WhenAll和Task.WhenAny的使用,可是在實際中有什麼做用呢?
首先,如前所所述,Task.WhenAll()和Task.WhenAny()是Task.WaitAll()和Task.WaitAny()的異步版本,可是呢,Task.WaitAll()和Task.WaitAny()是沒有返回值的,Task.WhenAll()
和Task.WhenAny()
是有返回值,返回值類型是一個Task對象,因此你能夠給其一個延續任務,即在異步等待的Task完成後,指定繼續執行的Task。
因此當調用的異步方法沒有相互的依賴的時候,通常仍是使用WhenAll(),來等待異步方法,同時也能夠給全部的異步方法結束後添加一個延續任務!
示例:爲異步等待後添加延續工做
static void Main(string[] args) { Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2 }:Task以前..."); Task<int> t1 = DoAsync(2000); Task<int> t2 = DoAsync(6000); //Task.WhenAll(t1, t2).ContinueWith(t => Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2 }:延續任務,兩個異步操做返回值是一個int[],其中元素分別是{t.Result[0]}、{t.Result[1]}")); //Task.WhenAny(t1, t2).ContinueWith(t => Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2 }:延續任務,第一個完成的異步操做返回值是{t.Result.Result}")); for (int i = 0; i < 8; i++) { Thread.Sleep(1000); Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2}:循環中"); } Console.ReadKey(); } private static async Task<int> DoAsync(int num) { int result = await Task.Run(() => { Thread.Sleep(num); Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2}:異步操做之等待:{num}s"); return num; }); return result; }
說明1:
如果Task.WhenAll()
後的延續工做,則注意Task.WhenAll()
的返回的Task<TResult>
的Result
是TResult[]
類型
即多個Task的返回值,存放在一個數組中
運行結果:
說明2:
如果Task.WhenAny()
後的延續工做,則注意Task.WhenAny()
的返回的是Task<Task>
類型,即其Result
是Task<TResutl>
類型,因此爲了獲取第一結束的Task的返回值,須要:t.Result.Result
。
運行結果:
說明3:
Task.WhenAll(Task[] tasks).ContinueWith(Action<Task>)
等價於Task.Factory.ContinueWhenAll(Task[] tasks, Action<Tast>)
Task.WhenAny(Task[] tasks).ContinueWith(Action<Task>)
等價於Task.Factory.ContinueWhenAny(Task[] tasks, Action<Tast>)
通常程序中對異常的處理使用try{……} catch{……}
首先看一個捕獲異常失敗的示例:
在Main()中調用ThrowEx(2000,"這是異常信息")
,第一個參數是ThrowEx中的Tast延遲的時間,第二個參數是ThrowEx中的拋出異常的信息。
static void Main(string[] args) { try { ThrowEx(2000, "這是異常信息"); } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.ReadKey(); } private async static Task ThrowEx(int ms, string message)//注意這裏的返回值類型爲Task,如果寫成void也是沒法在catch語句中捕獲異常,可是運行vs會報錯(見:說明2) { await Task.Delay(ms).ContinueWith(t => Console.WriteLine("hello word")); throw new Exception(message); }
說明1:
多打斷點,就能夠發現爲什麼捕捉不到異常了。
由於當調用ThrowEx(2000, "異常信息")
,開始異步方法中的await表達式,
即建立一個新的線程,在後臺執行await表達式,而主線程中此時會繼續執行ThrowEx(2000, "異常信息");
後的代碼:catch (Exception ex)
,
此時,異步方法中還在等待await表達式的執行,尚未拋出咱們本身定義的異常,因此此時壓根就沒有異常拋出,因此catch語句也就捕獲不到異常,
而當異步方法拋出異常,此時主線程中catch語句已經執行完畢了!
說明2:
在1.基本語法-返回值類型中咱們說道:在編寫異步方法的時候,有時後沒有返回值,也不須要查看異步操做的狀態,咱們設置返回值類型爲void
,並且稱之爲「調用並忘記」。然而這種異步代碼編寫方式,並不值得提倡。
爲何呢?如果沒有返回值,異步方法中拋出的異常就沒法傳遞到主線程,在主線程中的catch
語句就沒法捕獲拍異常!因此異步方法最好返回一個Task類型。
異步方法有返回值的時候,拋出的在異常會置於Task對象中,能夠經過task.IsFlauted屬性查看是否有異常,在主線程的調用方法中,使用catch
語句能夠捕獲異常!
正確示例:只須要給調用的異步方法,添加一個await
。
static void Main(string[] args) { try { await ThrowEx(2000, "這是異常信息"); } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.ReadKey(); } private async static Task ThrowEx(int ms, string message) { await Task.Delay(ms).ContinueWith(t => Console.WriteLine("hello word")); throw new Exception(message); }
使用Task.WhenAll()
處理多個異步方法中拋出異常
當有多個異步操做,使用WhenAll異步等待,其返回值是一個Task類型對象,該對象的異常爲AggregateException
類型的異常,每個的子任務(即WhenAll所等待的全部任務)拋出的異常都是包裝在這一個AggregateException中,如果須要打印其中的異常,則須要遍歷AggregateException.InnerExceptions
static void Main(string[] args) { Task taskResult = null;//注意由於在catch語句中須要使用這個WhenAll的返回值,因此定義在try語句以外。 try { Console.WriteLine($"當前的線程Id:{Thread.CurrentThread.ManagedThreadId,2}:do something before task"); Task t1 = ThrowEx($"這是第一個拋出的異常信息:異常所在線程ID:{Thread.CurrentThread.ManagedThreadId,2}", 3000); Task t2 = ThrowEx($"這是第二個拋出的異常信息:異常所在線程ID:{Thread.CurrentThread.ManagedThreadId,2}", 5000); await (taskResult = Task.WhenAll(t1, t2)); } catch (Exception)//注意這裏捕獲的異常只是WhenAll()等待的異步任務中第一拋出的異常 { foreach (var item in taskResult.Exception.InnerExceptions)//經過WhenAll()的返回對象的Exception屬性來查閱全部的異常信息 { Console.WriteLine($"當前的線程Id:{Thread.CurrentThread.ManagedThreadId,2}:{item.Message}"); } } } private async static Task ThrowEx(int ms, string message) { await Task.Delay(ms).ContinueWith(t => Console.WriteLine("hello word")); throw new Exception(message); }
運行結果:
說明 :
Task.WhenAll()
返回的Task對象中的Exception屬性是AggregateException
類型的異常.
注意,該訪問該異常InnerExcption
屬性則只包含第一個異常,該異常的InnerExcptions
屬性,則包含全部子任務異常.
首先多個異步操做的異常會包裝在一個AggregateException異常中,被包裝的異常能夠也是AggregateException類型的異常,因此如果須要打印異常信息可能須要循環嵌套,比較麻煩。
故可使用 AggregateException.Flatten()
打破異常的嵌套。
注意,凡是使用await
等待的異步操做,它拋出的異常沒法使用catch(AggregateException)
捕獲!
只能使用catch (Exception)
對異常捕獲,在經過使用Task的返回值的Exception屬性對異性進行操做。
固然你要是想使用catch(AggregateException)
捕獲到異常,則可使用task.Wait()方法等待異步任務,則拋出的異常爲AggregateException類型的異常
示例:(完整Demo)
catch (AggregateException ae)//AggregateException類型異常的錯誤信息是「發生一個或多個異常」 { foreach (var exception in ae.Flatten().InnerExceptions) //使用AggregateException的Flatten()方法,除去異常的嵌套,這裏你也能夠測試不使用Flatten(),拋出的信息爲「有一個或多個異常」 { if (exception is TestException) { Console.WriteLine(exception.Message); } else { throw; } } }
如果須要針對AggregateException
中某個或是某種異常進行處理,可使用Handle()
方法
Handel()
的參數是一個有返回值的委託:Func<Exception,bool> predicate
示例:(完整Demo)
catch (Exception) { t.Exception.Handle(e => { if (e is TestException)//若是是TestException類型的異常 { Console.WriteLine(e.Message); } return e is TestException; }); }
不要把多線程和異步兩個概念混爲一談!異步是最終目的,多線程只是咱們實現異步的一種手段!
首先,使用異步和多線程均可以免線程的阻塞,可是原理是不同的。
多線程:當前線程中建立一個新的線程,當前線程線程則不會被鎖定了,可是鎖定新的線程執行某個操做。換句話說就是換一條線程用來代替本來會被鎖定的主線程!優勢就是,線程中的處理程序的運行順序仍是從上往下的,方便理解,可是線程間的共享變量可能形成死鎖的出現。
異步:異步概念是與同步的概念相對的,簡單的說就是:調用者發送一個調用請求後,調用者不須要等待被被調用者返回結果而能夠繼續作其餘的事情。實現異步通常是經過多線程,可是還能夠經過多進程實現異步!
多線程和異步能夠解決不一樣的問題
可是首先咱們要區分當前須要長時間操做的任務是:CPU密集型仍是IO密集型,具體可參考長時間的複雜任務又分爲兩種
CPU Bound:使用多線程
IO Bound:使用異步
如今的 ASP .NET MVC項目中,若使用的.net中的方法有異步版本的就儘可能使用異步的方法。
在MVC項目中異步編程能夠大大的提升網站服務器的吞吐量,便可以能夠大大的提升網站的同時受理的請求數量
據傳,MVC網站如果異步編程則能夠提升網站的同時訪問量的2.6倍。
注意是提升網站的同時訪問量,而不是提升網站的訪問速度!
在MVC項目異步編程的的方式和在控制檯中同樣,使用async和await,基於任務的異步編程模式
簡單的示例:同步和異步兩種方式分別讀取桌面1.txt文件
//同步操做 public ActionResult Index() { string msg = ""; using (StreamReader sr = new StreamReader(@"C:\Users\shanzm\Desktop\1.txt", Encoding.Default)) { while (!sr.EndOfStream) { msg = sr.ReadToEnd(); } } return Content(msg); } //異步操做 public async Task<ActionResult> Index2() { string msg = ""; using (StreamReader sr = new StreamReader(@"C:\Users\shanzm\Desktop\1.txt", Encoding.Default)) { while (!sr.EndOfStream) { msg = await sr.ReadToEndAsync();//使用異步版本的方法 } } return Content(msg); }