async是一個語法糖,用來簡化異步編程,主要是讓異步編程在書寫上接近於同步編程。總的來收,在await的時候,至關於附加上了一個.ContinueWith()。html
至於爲何async可以提升吞吐,是由於經過async方法返回一個Task對象,IIS縮減了工做線程的處理時間長短(切換到了其餘線程,且沒有阻塞當前線程),從而提升了單位時間的處理量。這裏還有其餘的一些細節,詳情見這篇博文:web
【http://www.cnblogs.com/rosanshao/p/3728108.html】數據庫
關於async的使用,參考這篇博文:編程
【http://www.asp.net/mvc/overview/performance/using-asynchronous-methods-in-aspnet-mvc-4】api
博主曾經花了一個下午點時間測試性能,就是沒有得到指望的結果,用的就是此博文中舉出的反例。當時博主心想「既然TPL中的Task+async就能提升性能,那麼爲何EF還要特意的提供XXXAsync方法?這不是讓別人更加困惑麼?」因此博主就打算不用數據庫,簡單擼一個Task測一測,看看是否是和我想象中的通常逆天。架構
根據這篇博客的描述,IIS的線程分爲工做線程和IO線程兩種,其中工做線程總數被限制在一個閾值,因此減小工做線程的利用效率能夠提升吞吐。而在asp.net中,切換線程就分爲兩種:工做線程->IO線程,工做線程->工做線程(反例)。假定一個工做線程每使用async以前每請求工做1秒,經過切換,IO線程工做的時候,他去處理其餘請求,把平均工做時間降爲了0.5秒,這樣吞吐理想狀況下就翻倍了。可是...若是是工做線程->工做線程,雖然對於單個線程而言是減小了,可是其餘工做線程又會扔活過來,整體來講沒有變化,反而由於交接的問題,性能有所降低...併發
首先定義一個提供各類操做的輔助類。mvc
public class BaseFairHelper { public Task<string> SayHelloTask() { return Task<string>.Factory.StartNew(() => { Thread.Sleep(1000); return "Hello"; }); } public async Task<string> SayHelloAsync() { return await Task<string>.Factory.StartNew(() => { Thread.Sleep(1000); return "Hello"; }); } public string SayHello() { Thread.Sleep(1000); return "Hello"; } }
1.基礎測試app
假定咱們任意啓動一個Task就能夠達到解放IIS工做線程的目的,那麼,對於兩個Action,一個執行工做量1的同步操做,一個執行工做量1的同步操做外帶一個工做量1的一步操做,這兩個Action在吞吐以及性能表現上應該相差無幾。代碼以下:asp.net
/// <summary> /// 異步 /// </summary> /// <returns></returns> public ActionResult BaseAsync() { var helper = new BaseFairHelper(); var task1 = helper.SayHelloTask(); var str = helper.SayHello(); task1.Wait(); return Content(str); } /// <summary> /// 對照 /// </summary> /// <returns></returns> public ActionResult BaseAsync_() { var helper = new BaseFairHelper(); var task1 = helper.SayHelloTask(); var task2 = helper.SayHelloTask(); var str = helper.SayHello(); Task.WaitAll(task1, task2); return Content(str); } /// <summary> /// 基礎對照 /// </summary> /// <returns></returns> public ActionResult Base() { var helper = new BaseFairHelper(); return Content(helper.SayHello()); }
而後使用VS的負載測試,測試模式選爲增量,結果以下:
工做量 | 狀況 | 吞吐量(min) | 吞吐量(max) | 時長per請求(max) | 時長per請求(min) | 吞吐均值 | 時長均值 |
(base)1 | 同步 | 8 | 200 | 1.02 | 1.01 | 145 | 1.02 |
(baseasync)2 | 同步+異步 | 8 | 120 | 1.76 | 1.01 | 98.7 | 1.53 |
(baseasync_)3 | 同步+異步x2 | 0 | 89.4 | 2.59 | 1.01 | 69.5 | 2.1 |
能夠發現性能相差明顯,可是在低併發狀況下,性能表現是咱們預期的,高併發的時候,則否則。最大吞吐也不是咱們預期的。這點上能夠支持「IIS工做線程」有限的觀點。
2.Fair測試
以上,這是一組對比測試,工做量並不一樣,如今進行一組工做量相同的測試。其中一個Action執行同步x2的操做,另外一個執行同步+異步組合的操做。代碼以下:
public ActionResult FairAsync() { var helper = new BaseFairHelper(); var task = helper.SayHelloTask(); var str = helper.SayHello(); task.Wait(); return Content(str); } public ActionResult Fair() { var helper = new BaseFairHelper(); var str = helper.SayHello(); str = helper.SayHello(); return Content(str); }
一樣適用負載測試,測試模式選爲高併發(200用戶數):
工做量 | 狀況 | 吞吐量(min) | 吞吐量(max) | 時長per請求(max) | 時長per請求(min) | 吞吐均值 | 時長均值 |
(fair)2 | 同步 | 8 | 112 | 2.03 | 2 | 85 | 2.01 |
(fairasync)2 | 同步+異步 | 20 | 125 | 1.85 | 1 | 107 | 1.59 |
和低併發(25用戶數):
工做量 | 狀況 | 吞吐量(min) | 吞吐量(max) | 時長per請求(max) | 時長per請求(min) | 吞吐均值 | 時長均值 |
(fair)2 | 同步 | 1 | 12.6 | 2.03 | 2 | 10.7 | 2.02 |
(fairasync)2 | 同步+異步 | 2 | 25 | 1.02 | 1 | 21.3 | 1.01 |
能夠看到,因爲工做線程爭用,致使使用Task的異步方案在高併發的狀況下,單個請求的性能有所降低(時長從1->1.85),這也從側面證實了以上的觀點。
這裏是我參考的文章:【http://www.dotnetcurry.com/aspnet-mvc/948/webapi-async-performance-aspnet-mvc-application】
以及這篇文章附帶的代碼:【http://pan.baidu.com/s/1ntxNX4t】
博主針對數據庫(EF)的async作了不少次實驗,結果發現同步和異步在吞吐以及性能表現上幾乎一致(參考文章末尾附件中的測試結果截圖)。因而最終返回這篇文章,並針對這篇文章中的代碼進行測試,同時結合本身的思考從新編寫了測試——結果仍然沒有感覺到duang一下的特效。因此暫時不糾結了。
【此處應該有跟進和更新】
對於如下兩個異步方法:
public class AsyncMethods { public static async Task<string> Async1() { return await Task<string>.Factory.StartNew((t) => { Task.Delay(1000).Wait(); return "hello"; }, null); } public static async Task<string> Async2() { return await Task<string>.Factory.StartNew(t => { Thread.Sleep(1000); return "hello"; }, null); } }
1.對多個async方法進行同步等待
[ActionName("IndexAsync2")] public async Task<ActionResult> IndexAsync2() { var task1 = AsyncMethods.Async1(); var task2 = AsyncMethods.Async2(); await Task.WhenAll(task1, task2); return Content(task2.Result); }
2.有序執行多個async
[ActionName("IndexAsync1")] public async Task<ActionResult> IndexAsync1() { string result = await AsyncMethods.Async1(); result = result + await AsyncMethods.Async2(); return Content(result); }
3.死鎖(反例)
簡單將await方法遷移到同步方法中,都會致使線程死鎖(ASP.NET環境下)。因爲異步方法執行完成後的操做要求回到調用的上下文(線程),會等待調用上下文。而Wait()方法表示等待異步方法完成。因此你等我我等你,死鎖。
public ActionResult Index1() { AsyncMethods.Async1().Wait(); return Content(""); } public ActionResult Index2() { var task1 = AsyncMethods.Async1(); var task2 = AsyncMethods.Async2(); Task.WhenAll(task1, task2); return Content(task1.Result); }
爲什麼async可以防止ASP.NET工做線程等待
參考這篇文章:【http://blog.stevensanderson.com/2008/04/05/improve-scalability-in-aspnet-mvc-using-asynchronous-requests/】的圖。
這是並行編程的狀況,總的來講就是充分利用CPU,我的認爲這更多的是Task的功勞。async這個關鍵字更多的像是將一些列的ContinueWith連鎖在同一個線程(上下文)之上,防止線程切換。
通過多日的實驗和糾結(慚愧),對async的見解有了點轉變。async關鍵字如今給個人感受,更像是從「骨子裏」的異步,由於調用async方法的時候,要求調用方也指明async(或者你能夠開一個Task去執行...然而...太蠢)。這感受是讓C#的中的全部方法(指明async)天生就是異步架構的(無故想起了F#)。因此,爲Cqrs添加異步功能就分爲兩塊:
1.爲CommandBus添加一個SendAsync的方法
2.實現一個徹底基於異步的Cqrs【想法,想法,只是想法...】【此處應有後續跟進】
先擼第一個:
public interface ICommandBus { void Send<T>(T command) where T : ICommand; Task SendAsync<T>(T command) where T : ICommand; } void ICommandBus.Send<T>(T command) { var handler = CommandHandlerSearcher.Find<T>(); #region auditing var auditInfo = CommandEventAuditInfo.StartNewForCommand<T>(handler.GetType()); auditInfo.Start(); #endregion handler.Execute(command); #region audting auditInfo.Stop(); #endregion Test.Configuration.AuditStorage.Save(auditInfo); } public ICommandHandlerSearcher CommandHandlerSearcher { get; set; } Task ICommandBus.SendAsync<T>(T command) { ICommandBus bus = this; return Task.Factory.StartNew(() => bus.Send(command)); }
而後是測試結果:
同時,在修改Auditing支持異步的同時,發現了本身之前實現的Auditing有問題。
至於爲何不考慮實現EventBus支持異步...那是由於,博主當前的工做單元是基於線程的(簡單粗暴的將一個Command視爲原子操做)。
與async有關的代碼:【http://pan.baidu.com/s/1sjA7gbN】
此篇完成時,所使用的代碼:【http://pan.baidu.com/s/1sjsqiZV】