前不久,在工做中因为默认(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