微軟的Task已經出來好久了,一直沒有去研究,覺得就是和Thread差很少的東西。直到最近看到了Task的使用介紹,發現比Thread的語法要精煉多了,因而便在項目中用上了。數據庫
結果就出問題了,數據庫鏈接池用一段時間就滿了,排除了各類緣由,最後開始懷疑是否是Task有什麼鮮爲人知的隱患。api
因爲對Task的使用只是停留在開一個線程去執行一個不須要返回結果的任務這種階段。爲了查明是不是Task引發的線程池滿,便開始各類查資料。併發
最終的結果是,鏈接池盡是由於程序中的一個SqlConnection沒有關閉,和Task沒有半毛錢關係......異步
問題解決了。Task也研究的差很少了。async
下面咱們來談一下Task的使用.....函數
開啓task有如下三種方式,曾經一度糾結在到底該用哪一種方式來開始一個任務,最終發現其實在沒有特殊要求的狀況下,這三種方式除了語法不一樣外,執行方式和結果是同樣的測試
Task<int> t1 = Task.Factory.StartNew(() => { Task.Delay(1000); return 1; }); Task<int> t2 = new Task<int>(() => { Task.Delay(1000); return 1; }); t2.Start(); Task<int> t3 = Task.Run(() => { Task.Delay(1000); return 1; });
上面的示例返回一個數字,因此接收者是Task的泛型,一樣也能夠執行一個不帶返回結果的函數。網站
使用這種方式來獲取結果,主線程會等待Task執行完成。也就是說,用這種方式來獲取Task的返回結果,和不使用Task並無什麼區別。ui
可是須要注意的是,慎用.Result或者Wait來獲取Task的返回值,除非你明確地知道Task的代碼邏輯。咱們來看下面一段代碼spa
當點擊button2時,應用程序會卡死。由於在調用.Result時,UI線程會阻塞,
而咱們給GetResult的任務指出須要用UI線程來執行任務中的代碼。
UI線程在等待GetResult完成,卻又沒法去運行GetResult中的代碼。形成死鎖
先上一個測試代碼
static void Main(string[] args) { Console.WriteLine("程序啓動"); Task<int> t = GetNum(); Console.WriteLine("主線程繼續執行"); int num = t.Result; Console.WriteLine("主線程獲取到數字:" + num); } public static async Task<int> GetNum() { Console.WriteLine("GetNum函數進入"); int num = await Task.Run(() => { Task.Delay(1000); return 2; }); Console.WriteLine("GetNum函數獲取到數字:"+num); return num; }
await 運算符只能用於異步方法中,因此包含await運算符的方法都須要有async修飾符來修飾,稱之爲異步方法。
經過實驗,咱們看到在異步函數中,遇到await運算符以後,主線程就繼續往下執行了,更確切的解釋是,Main函數開始和GetNum函數並行執行,
直到獲取t.Result時,若GetNum()函數仍未執行完畢,Main函數則須要等待GetNum返回。
在GetNum函數中,await後面的代碼須要等待await的Task執行完成後方可執行,等同於下面不適用await的代碼
static void Main(string[] args) { Console.WriteLine("程序啓動"); Task<int> t = GetNumNoAwait(); Console.WriteLine("主線程繼續執行"); int num = t.Result; Console.WriteLine("主線程獲取到數字:" + num); Console.Read(); } public static Task<int> GetNumNoAwait() { Console.WriteLine("GetNum函數進入"); Task<int> t = Task<int>.Run(() => { Task.Delay(1000); return 2; }); t.ContinueWith(m => { Console.WriteLine("GetNum函數獲取到數字:" + m.Result); }); return t; }
經過await來獲取返回值的操做,比前一種方式的優勢在於他不會阻塞主線程的。這一點在winform或wpf等gui程序上能夠很明顯地提現出來
這是一個winform程序的代碼片斷,頁面中有兩個按鈕,咱們用maketext函數來模擬一個須要耗時的好比調用api獲取數據的操做。
當點擊button1時程序會一直等待結果返回,期間窗體沒法拖動
而用異步方法則不會阻塞主窗體的其餘操做
看過不少在Action中使用異步action的文章,並以此和未使用異步的Action來進行網站吞吐量的對比。
大概的代碼相似於下面這樣
最終都會得出一個結論,以上代碼的吞吐量要遠遠高於未使用異步的
當時我就很不解,await就是在等待異步代碼執行完成,並不會釋放請求佔用的線程,爲何會提高網站的吞吐量呢?
通過各類實驗,仍然沒法來證實以上代碼的寫法會使得網站的吞吐量更高,反而大多數狀況下,效率稍微低了一些
(剛剛看過一本書中有介紹,一般的方法調用比使用async關鍵字的一樣方法調用要快上40-50倍。因此異步函數在合適的場景被正確地使用也是很是重要的)
最終看了Msdn上關於異步控制器的介紹,方纔找到正確的寫法
如下是截取MSdn上的代碼片斷
首先使用 AsyncManager.OutstandingOperations.Increment()函數來設定未完成的請求操做,默認是1,而後每個異步操做完成,經過Decrement來使計數器減1,當計數器歸零以後,則會調用xxxCompleted函數來返回結果。
這樣解釋就行的通了,當執行完NewsAsync中的代碼以後,請求線程就會釋放,直到異步函數執行完成,系統會從新獲取一個線程經過NewsCompleted來返回給客戶端執行結果。
下面是個人測試代碼
public class TaskTestController : AsyncController { public void GetDataAsync() { AsyncManager.OutstandingOperations.Increment(); WebClient client = new WebClient(); client.DownloadDataCompleted += (sender, e) => { AsyncManager.Parameters["bytelength"] = e.Result.Length; AsyncManager.OutstandingOperations.Decrement(); }; client.DownloadDataAsync(new Uri("https://docs.microsoft.com/zh-cn/dotnet/framework/get-started/")); } public int GetDataCompleted(int bytelength) { return bytelength; } }
public class TaskTest1Controller : Controller { public int GetData() { WebClient client = new WebClient(); var rsu = client.DownloadData(new Uri("https://docs.microsoft.com/zh-cn/dotnet/framework/get-started/")); return rsu.Length; ; } }
而後我進行模擬1000個併發2000條請求,下面是測試結果
這裏就能夠看到異步控制器的優點已經顯露出來了
而後我將iis的最大併發設置爲10,模擬了一個20併發200條請求的操做,
異步控制器用時3.001s,失敗0條
普通控制器用時4.551s,失敗8條
測試完成,但願對有須要的人有所幫助