为什么我们要使用Async、Await关键字
前不久,在工作中由于默认(xihuan)使用Async、Await关键字受到了很多质问,所以由此引发这篇博文“为什么我们要用Async/Await关键字”,请听下面分解:
Async/Await关键字
Visual Studio(.net framework 4.5)提供了异步编程模型,相比之前实现方式,新的异步编程模型降低了使用的复杂度并且更容易维护和调试,编译器代替用户做了很多复杂的工作来实现异步编程模型[^4]。
- 调用异步方法AccesstheWebAsync
- 创建HttpClient实例,并使用HttpClient获取异步数据。
- 利用Task执行获取数据方法(假设获取数据需要很长时间),不阻塞当前线程,getStringTask代表进行中的任务。
- 因为getStringTask还没有使用await 关键字,使之可以继续执行不依赖于其返回结果的其他任务,同步执行DoIndependentWork。
- 当同步任务DoIndependentWork执行完毕之后,返回调用给AccessTheWebAsync线程。
- 使用await强制等待getStringTask完成,并获取基于Task<String>类型的返回值。(如果getStringTask在同步方法DoIndependentWork执行之前完成,调用会返回给AccessTheWebAsync线程,调用await将会执行不必要的挂起操作)
- 当获取web数据之后,返回结果记录在Task中并返回给await调用处(当然,返回值并没有在第二行返回)。
- 获取数据并返回计算结果。
刨根问底
以上是官方给的说明文档,例子详尽表达清楚,但是有一个问题没有解决(被证明):
1. 当线程在await处返回给线程池之后,该线程是否“真的”被其他请求所消费?
2. 服务器线程资源是一定的,是谁在真正执行Await所等待的操作,或者说异步IO操作?
3. 如果使用IO线程执行异步IO操作,相比线程池的线程有什么优势?或者说异步比同步操作优势在哪里?
前提条件:
1. 相对Console应用程序来说,可以使用ThreadPool的SetMaxThread来模拟当前进程所支持的最大工作线程和IO线程数。
2. 通过ThreadPool的GetAvailableThreads可以获得当前进程工作线程和IO线程的可用数量。
3. ThreadPool是基于进程的,每一个进程有一个线程池,IIS Host的进程可以单独管理线程池。
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 = * ; 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 = ; i < Environment.ProcessorCount * ; i++)
{
Task.Factory.StartNew(SyncJob, new {Id = i});
}
}); Console.WriteLine("==========================================="); LogRunningTime(() =>
{
for (int i = ; i < Environment.ProcessorCount * ; 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 + ];
int numRead;
while ((numRead = await source.ReadAsync(buffer, , buffer.Length)) != )
{
await destination.WriteAsync(buffer, , numRead);
}
} static void CopyFileSync(FileStream source, FileStream destination)
{
var buffer = new byte[BufferSize + ];
int numRead;
while ((numRead = source.Read(buffer, , buffer.Length)) != )
{
destination.Write(buffer, , numRead);
}
} static void LogRunningTime(Action callback)
{
var awailableWorkingThreadCount = ;
var awailableAsyncIoThreadCount = ; var watch = Stopwatch.StartNew();
watch.Start(); callback(); while (awailableWorkingThreadCount != maxWorkerThreads)
{
Thread.Sleep();
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时,方法将转换为同步方法.
基于IIS Host的应用程序
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
结论:
- 同步方法应用场景:
- 请求处理非常快
- 代码简洁大于代码效率
- 主要是基于CPU耗时操作
- 异步方法应用场景:
- 基于Network或者I/O类型操作,而非CPU耗时操作
- 当阻塞操作成为瓶颈时,通过异步方法能使IIS处理更多的请求
- 并行化处理比代码简洁更重要
- 提供一种机制可以让用户取消长时间运行的请求
更多线程优化
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
为什么我们要使用Async、Await关键字的更多相关文章
- 异步编程Async/await关键字
异步编程Async \await 关键字在各编程语言中的发展(出现)纪实. 时间 语言版本 2012.08.15 C#5.0(VS2012) 2015.09.13 Python 3.5 2016.03 ...
- .net 异步编程async & await关键字的思考
C# 5.0引入了两个关键字 async和await,这两个关键字在很大程度上帮助我们简化了异步编程的实现代码,而且TPL中的task与async和await有很大的关系 思考了一下异步编程中的asy ...
- 教你正确打开async/await关键字的使用
这段时间在项目开发中看到了一些async/await的使用,在aspnet core的host组件源码中也看到了许多的async/await代码.在开发时,正确的使用了async/await是可以提高 ...
- .Net异步关键字async/await的最终理解
由于之前的项目中自己突然想试试异步action,于是使用了一下,突然就对异步action的执行流程以及原理及其好处产生了兴趣,再参考了一些文章之后,就做了下归类. 我们可以不需要太深入的理解底层,但是 ...
- 关于异步执行(Async/await)的理解(转发)
原文地址: http://blog.jobbole.com/85787/ 同步编程与异步编程 通常情况下,我们写的C#代码就是同步的,运行在同一个线程中,从程序的第一行代码到最后一句代码顺序执行.而异 ...
- Python协程:从yield/send到async/await
这个文章理好了脉落. http://python.jobbole.com/86069/ 我练 习了一番,感受好了很多... Python由于众所周知的GIL的原因,导致其线程无法发挥多核的并行计算能力 ...
- async/await 异步编程(转载)
转载地址:http://www.cnblogs.com/teroy/p/4015461.html 前言 最近在学习Web Api框架的时候接触到了async/await,这个特性是.NET 4.5引入 ...
- async/await 异步编程
前言 最近在学习Web Api框架的时候接触到了async/await,这个特性是.NET 4.5引入的,由于之前对于异步编程不是很了解,所以花费了一些时间学习一下相关的知识,并整理成这篇博客,如果在 ...
- async/await的实质理解
async/await关键字能帮助开发者更容易地编写异步代码.但不少开发者对于这两个关键字的使用比较困惑,不知道该怎么使用.本文就async/await的实质作简单描述,以便大家能更清楚理解. 一.a ...
随机推荐
- webgl自学笔记——光照
在Webgl中我们使用顶点着色器和片元着色器来为我们的场景创建光照模型.着色器允许我们使用数学模型来控制如何照亮我们的场景. 最好有线性代数的相关知识. 本章中: 光源.法线.材料 光照和着色的区别 ...
- sqlserver 处理百万级以上的数据处理与优化
一处理百万级以上的数据提高查询速度的方法: 1.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描. 2.对查询进行优化,应尽量避免全表扫描,首先应 ...
- js中的数组排序
js数组冒泡排序,快速排序的原理以及实现 冒泡排序: 随便从数组中拿一位数和后一位比较,如果是想从小到大排序,那么就把小的那一位放到前面,大的放在后面,简单来说就是交换它们的位置,如此反复的交换位 ...
- Echarts折线图表断点如何补全
Echarts折线图如何补全断点以及如何隐藏断点的title 做报表的时候,尤其是做图表的时候时常会碰到某一记录的值中缺少某个时间段(比如月份或季度)的值,导致图表显示残缺不全,for example ...
- Selenium模拟JQuery滑动解锁
滑动解锁一直做UI自动化的难点之一,我补一篇滑动解锁的例子,希望能给初做Web UI自动化测试的同学一些思路. 首先先看个例子. https://www.helloweba.com/demo/2017 ...
- HTML5之placeholder属性以及如何更改placeholder属性中文字颜色
今天在群里看到群友问了一个这样的问题,就是如何更改placeholder属性中文字的颜色,以前用过这属性,却是没更改过颜色,于是便试了试,中途遇到些问题,查找资料后特来总结一下. 熟悉HTML5的人应 ...
- 【css】过度效果
http://kissygalleryteam.github.io/girlLink/doc/demo/index.html
- Oracle的sessions和processes的数计算公式
Oracle的sessions和processes的数计算公式 原作者链接地址:http://blog.csdn.net/zengmuansha/article/details/7581771 Ora ...
- 分享一个PHP文件上传类
该类用于处理文件上传,可以上传一个文件,也可以上传多个文件. 包括的成员属性有: private $path = "./uploads"; //上传文件保存的路径 private ...
- Effective Java通俗理解(持续更新)
这篇博客是Java经典书籍<Effective Java(第二版)>的读书笔记,此书共有78条关于编写高质量Java代码的建议,我会试着逐一对其进行更为通俗易懂地讲解,故此篇博客的更新大约 ...