前不久,在工做中因爲默認(xihuan)使用Async、Await關鍵字受到了不少質問,因此由此引起這篇博文「爲何咱們要用Async/Await關鍵字」,請聽下面分解:html
Visual Studio(.net framework 4.5)提供了異步編程模型,相比以前實現方式,新的異步編程模型下降了使用的複雜度而且更容易維護和調試,編譯器代替用戶作了不少複雜的工做來實現異步編程模型[^4]。git
以上是官方給的說明文檔,例子詳盡表達清楚,可是有一個問題沒有解決(被證實):github
1. 當線程在await處返回給線程池以後,該線程是否「真的」被其餘請求所消費?web
2. 服務器線程資源是必定的,是誰在真正執行Await所等待的操做,或者說異步IO操做?編程
3. 若是使用IO線程執行異步IO操做,相比線程池的線程有什麼優點?或者說異步比同步操做優點在哪裏?服務器
前提條件:多線程
1. 相對Console應用程序來講,可使用ThreadPool的SetMaxThread來模擬當前進程所支持的最大工做線程和IO線程數。mvc
2. 經過ThreadPool的GetAvailableThreads能夠得到當前進程工做線程和IO線程的可用數量。異步
3. ThreadPool是基於進程的,每個進程有一個線程池,IIS Host的進程能夠單獨管理線程池。async
4. 若是要真正意義上的模擬異步IO線程操做文件須要設置FileOptions.Asynchronous,而不是僅僅是使用BeginXXX一類的方法,詳情請參考[^1]的異步IO線程。
5. 在驗證同步和異步調用時,執行的任務數量要大於當前最大工做線程的2倍,這樣才能夠測出當Await釋放工做線程後,其餘請求可繼續利用該線程。
結論:
1. Await使用異步IO線程來執行,異步操做的任務,釋放工做線程回線程池。
2. 線程池分爲工做線程和異步IO線程,分別執行不一樣級別的任務。
3. 使用Await來執行異步操做效率並不老是高於同步操做,須要根據異步執行長短來判斷。
4. 當工做線程和IO線程相互切換時,會有必定性能消耗。
各位能夠Clone代碼,並根據Commit去Review代碼,相信你們能理解代碼意圖,若是不能,請留言,我改進:)
[GitHubRepo](https://github.com/Cuiyansong/Why-To-Use-Async-Await-In-DotNet.git)
using System; using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; namespace AsyncAwaitConsole { class Program { static int maxWorkerThreads; static int maxAsyncIoThreadNum; const string UserDirectory = @"files\"; const int BufferSize = 1024 * 4; static void Main(string[] args) { AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) => { Directory.Delete("files", true); }; maxWorkerThreads = Environment.ProcessorCount; maxAsyncIoThreadNum = Environment.ProcessorCount; ThreadPool.SetMaxThreads(maxWorkerThreads, maxAsyncIoThreadNum); LogRunningTime(() => { for (int i = 0; i < Environment.ProcessorCount * 2; i++) { Task.Factory.StartNew(SyncJob, new {Id = i}); } }); Console.WriteLine("==========================================="); LogRunningTime(() => { for (int i = 0; i < Environment.ProcessorCount * 2; i++) { Task.Factory.StartNew(AsyncJob, new { Id = i }); } }); Console.ReadKey(); } static void SyncJob(dynamic stateInfo) { var id = (long)stateInfo.Id; Console.WriteLine("Job Id: {0}, sync starting...", id); using (FileStream sourceReader = new FileStream(UserDirectory + "BigFile.txt", FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize)) { using (FileStream destinationWriter = new FileStream(UserDirectory + $"CopiedFile-{id}.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, BufferSize)) { CopyFileSync(sourceReader, destinationWriter); } } Console.WriteLine("Job Id: {0}, completed...", id); } static async Task AsyncJob(dynamic stateInfo) { var id = (long)stateInfo.Id; Console.WriteLine("Job Id: {0}, async starting...", id); using (FileStream sourceReader = new FileStream(UserDirectory + "BigFile.txt", FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, FileOptions.Asynchronous)) { using (FileStream destinationWriter = new FileStream(UserDirectory + $"CopiedFile-{id}.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, BufferSize, FileOptions.Asynchronous)) { await CopyFilesAsync(sourceReader, destinationWriter); } } Console.WriteLine("Job Id: {0}, async completed...", id); } static async Task CopyFilesAsync(FileStream source, FileStream destination) { var buffer = new byte[BufferSize + 1]; int numRead; while ((numRead = await source.ReadAsync(buffer, 0, buffer.Length)) != 0) { await destination.WriteAsync(buffer, 0, numRead); } } static void CopyFileSync(FileStream source, FileStream destination) { var buffer = new byte[BufferSize + 1]; int numRead; while ((numRead = source.Read(buffer, 0, buffer.Length)) != 0) { destination.Write(buffer, 0, numRead); } } static void LogRunningTime(Action callback) { var awailableWorkingThreadCount = 0; var awailableAsyncIoThreadCount = 0; var watch = Stopwatch.StartNew(); watch.Start(); callback(); while (awailableWorkingThreadCount != maxWorkerThreads) { Thread.Sleep(500); ThreadPool.GetAvailableThreads(out awailableWorkingThreadCount, out awailableAsyncIoThreadCount); Console.WriteLine("[Alive] working thread: {0}, async IO thread: {1}", awailableWorkingThreadCount, awailableAsyncIoThreadCount); } watch.Stop(); Console.WriteLine("[Finsih] current awailible working thread is {0} and used {1}ms", awailableWorkingThreadCount, watch.ElapsedMilliseconds); } } }
注:Async/Await並無建立新的線程,而是基於當前同步上線文的線程,相比Thread/Task或者是基於線程的BackgroundWorker使用起來更方便。Async關鍵字的做用是標識在Await處須要等待方法執行完成,過多的await不會致使編譯器錯誤,但若是沒有await時,方法將轉換爲同步方法.
1. IIS 能夠託管ThreadPool,經過在IIS Application Pool中增長,而且能夠設置Working Thread 和 Async IO Thread 數目。
2. 服務端接受請求並從線程池中獲取當前閒置的線程進行處理,若是是同步處理請求,當前線程等待處理完成而後返回給線程池. 服務器線程數量有限,當超過IIS所能處理的最大請求時,將返回503錯誤。
3. 服務端接受請求並異步處理請求時,當遇到異步IO類型操做時,當前線程返回給線程池。當異步操做完成時,從線程池中拿到新的線程並繼續執行任務,直至完成後續任務[^7]。
例如,在MVC Controller中加入awaitable方法,證實當遇到阻塞任務時,當前線程當即返回線程池。當阻塞任務完成時,將從線程池中獲取新的線程執行後續任務:
var availableWorkingThreadCount = 0;
var availableAsyncIoThreadCount = 0;
ThreadPool.GetAvailableThreads(out availableWorkingThreadCount, out availableAsyncIoThreadCount);
AddErrors(new IdentityResult(string.Format("[IIS Host] Thread Id {0}, ThreadPool Thread: {1}",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread)));
AddErrors(new IdentityResult(string.Format("[IIS Host] current working thread: {0}, current async thread: {1}", availableWorkingThreadCount, availableAsyncIoThreadCount)));
HttpClient httpClient = new HttpClient();
var response = httpClient.GetStringAsync("https://msdn.microsoft.com/en-us/library/system.threading.thread.isthreadpoolthread(v=vs.110).aspx");
await response;
AddErrors(new IdentityResult(string.Format("[IIS Host] Thread Id {0}, ThreadPool Thread: {1}",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread)));
[IIS Host] Thread Id 4, ThreadPool Thread: True
[IIS Host] current working thread: 4094, current async thread: 1000
[IIS Host] Thread Id 9, ThreadPool Thread: True
結論:
Stephen Cleary 介紹了三種異步編程模型的規範[^5]:
1. Avoid Async Void, void和task<T>將產生不一樣的異常類型
2. 老是使用Async關鍵字
3. 使用Task.WaitXXX 代替Task.WhenXXX
4. Configure context 儘可能不要捕捉線程上下文,使用Task.ConfigureAwait(false)
[^1] 《CLR via C# Edition3》 25章線程基礎
[^3] 異步編程模型:https://msdn.microsoft.com/en-us/library/mt674882.aspx
[^4] C# Async、Await關鍵字:https://msdn.microsoft.com/library/hh191443(vs.110).aspx
[^5] Task Best Practice[Stephen Cleary]: https://msdn.microsoft.com/en-us/magazine/jj991977.aspx
[^6] 異步編程模型最佳實踐中文翻譯版:http://www.cnblogs.com/farb/p/4842920.html
[^7] 同步vs異步Controller:https://msdn.microsoft.com/en-us/library/ee728598%28v=vs.100%29.aspx
[^8] IIS 優化: https://docs.microsoft.com/en-us/aspnet/mvc/overview/performance/using-asynchronous-methods-in-aspnet-mvc-4