为什么我们要使用Async、Await关键字

前不久,在工作中由于默认(xihuan)使用Async、Await关键字受到了很多质问,所以由此引发这篇博文“为什么我们要用Async/Await关键字”,请听下面分解:

 

Async/Await关键字

Visual Studio(.net framework 4.5)提供了异步编程模型,相比之前实现方式,新的异步编程模型降低了使用的复杂度并且更容易维护和调试,编译器代替用户做了很多复杂的工作来实现异步编程模型[^4]。

 

 

 

  1. 调用异步方法AccesstheWebAsync

  2. 创建HttpClient实例,并使用HttpClient获取异步数据。

  3. 利用Task执行获取数据方法(假设获取数据需要很长时间),不阻塞当前线程,getStringTask代表进行中的任务。

  4. 因为getStringTask还没有使用await 关键字,使之可以继续执行不依赖于其返回结果的其他任务,同步执行DoIndependentWork。

  5. 当同步任务DoIndependentWork执行完毕之后,返回调用给AccessTheWebAsync线程。

  6. 使用await强制等待getStringTask完成,并获取基于Task<String>类型的返回值。(如果getStringTask在同步方法DoIndependentWork执行之前完成,调用会返回给AccessTheWebAsync线程,调用await将会执行不必要的挂起操作)

  7. 当获取web数据之后,返回结果记录在Task中并返回给await调用处(当然,返回值并没有在第二行返回)。

  8. 获取数据并返回计算结果。 

 

刨根问底

 

以上是官方给的说明文档,例子详尽表达清楚,但是有一个问题没有解决(被证明):

 

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 = 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时,方法将转换为同步方法. 

 

基于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章线程基础

[^2]百科-蜜蜂舞:http://baike.baidu.com/link?url=ixwDjgocRIg4MJGTQyR3mUC1fspHZtfPYEtADfJAJdC6X0xIVU4lJUe2iVvCNHEj3JeE1JalBCNyyPcVMdhaoyBFz_xXcLPMEJ_2iUcHjithF8_F8A9yI61EAzpmpYR4

[^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

 

原文地址:http://www.cnblogs.com/cuiyansong/p/7424997.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/323951.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一次惊险的跳槽面试经历(阿里/美团/头条/网易/有赞...)

转载自 一次惊险的跳槽面试经历&#xff08;阿里/美团/头条/网易/有赞...) 每次说因为生活成本的时候面试官都会很惊奇&#xff0c;难道有我们这里贵&#xff1f;好想直接给出下面这张图&#xff0c;厦门的房价真的好贵好贵好贵。。。 面试过程 有兴趣加入阿里的欢迎发简历…

利用bladex+avue实现一对多的关系

“大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂”今天&#xff0c;记录一篇技术文章吧&#xff0c;也是解决了好久才解决掉的&#xff08;说来也惭愧&#xff09;。涉及技术前端&#xff1a;vue&#xff0c;element ui后端框架&#xff…

.NET中的高性能应用

本文要点 .NET自4.0以来得到了大幅的性能提升&#xff0c;很值得重新考虑一下基于旧版本.NET框架所做的假定。在讨论性能时垃圾回收是个重复出现的主题&#xff0c;它带来了许多CLR和语言的提升&#xff0c;比如引用返回和ValueTask在内存分配上更细粒度度量的性能分析API会成…

利用bladex+avue实现下拉数据源展示

“ 大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;????雄雄的小课堂???? ”昨天给大家整理了下如何使用bladex实现多表查询的方法&#xff0c;今天我们趁热打铁&#xff0c;顺便看看下拉列表的实现。 需求 我们经常会有这样的需求&#xff0c;…

汇编语言(一)之反转字符串输出

BASED ADDRESSING反转输出 程序运行&#xff1a; 代码&#xff1a; datas segmentstring db BASED ADDRESSING$ count dw $-string-1 ;计算string的长度&#xff0c;$为当前地址&#xff0c;-1为去掉字符串结束符$srcsTip db SRC string:$ dstsTip db 0…

前后端分离项目部署上线详细教程

“ 大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;????雄雄的小课堂???? ”今天&#xff0c;给大家分享的是&#xff0c;SpringbootVue项目如何部署上线的详细步骤。 代码编辑器 前端&#xff1a;Webstorm 2021.1.2 后端&#xff1a;IntelliJ …

.Net Core2.0下使用Dapper遇到的问题

今天成功把.Net Framework下使用Dapper进行封装的ORM成功迁移到.Net Core 2.0上&#xff0c;在迁移的过程中也遇到一些很有意思的问题&#xff0c;值得和大家分享一下。下面我会还原迁移的每一个过程&#xff0c;以及在此过程中遇到的问题和处理这些问题的方法。 一、迁移前的…

汇编语言(二)之数值求和

输入一串数字&#xff0c;求和 运行结果&#xff1a; 程序代码&#xff1a; datas segmentx db ? y db ? z db ?xInputPrompt db Enter a number x$ yInputPrompt db 0dh,0ah,Enter a number y$ zOutputPrompt db 0dh,0…

avue中怎样隐藏新增和编辑的按钮

“大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;????雄雄的小课堂????”????‍♂️今天给大家分享的技术是&#xff1a;avue中如何设置新增和编辑的按钮隐藏掉。????‍????涉及技术????前端&#xff1a;avue????后端&…

.NET跨平台实践:Linux .Net Core自宿主应用程序瘦身记

一&#xff0c;.NET Core 自宿主应用程序个头有点大 发布.NET Core应用程序有两个方式&#xff0c;一种是“便携式”&#xff0c;一种是“自宿主式”。便携式发布时&#xff0c;目标程序不带.net core运行环境&#xff0c;所以“个头”很小&#xff0c;可能只有几十K几百K字节…

汇编语言(三)之判断数值是否大于42H并统计个数

在内存中一串数值&#xff0c;判断数值是否大于42H并统计个数 程序运行&#xff1a; 代码&#xff1a; datas segmentidata equ 100hnums db idata dup(41h,42h,43h,42h,41h,43h,30h)count dw $-numsup dw 0down dw 0upNumber …

idea打war的问题

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;&#x1f449;雄雄的小课堂&#x1f448;。 &#x1f468;‍&#x1f3eb;前言 今天&#xff0c;记录个到现在为止还没搞清的问题&#xff0c;这个问题浪费了我几个小时的时间&#xff0c;基本上昨天晚上…

汇编语言(四)之比较字符串

输入两个字符串&#xff0c;比较字符串是否相同 程序运行&#xff1a; 代码&#xff1a; datas segmentstring1MaxLength db 0ffh,0 string1 db 100h dup(?)string1Number dw 0 string2MaxLength db 0ffh,0 string2 db 100h dup(?) string2Number…

.NET平台微服务项目汇集

最近博客园出现了一篇文章《微服务时代之2017年五军之战&#xff1a;Net PHP谁先死》&#xff0c;掀起了一波撕逼&#xff0c;作者只是从一个使用者的角度来指点江山&#xff0c;这个姿势是不对的。.NET Core就是专门针对模块化的微服务架构而设计&#xff0c;在微服务架构这方…

idea打war包时,JDK版本的问题解决方式

“大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;????雄雄的小课堂????。”????‍????前言今天&#xff0c;记录个到现在为止还没搞清的问题&#xff0c;这个问题浪费了我几个小时的时间&#xff0c;基本上昨天晚上啥也没干&#xff0…

汇编语言(五)之数组中正数和负数分离

将数组中的正数和负数分离到两个数组 程序运行&#xff1a; 代码&#xff1a; datas segmenta dw -1,2,3,4,-2,-3,5,6,7,8,9,-10,13,15,-5,-24,-36,34,53,-90count dw ($-a)/2p dw 20 dup(?)n dw 20 dup(?)pNumber dw 0…

dotnet使用Selenium执行自动化任务

如果要做百度文库&#xff0c;百度贴吧&#xff0c;百度知道签到&#xff0c;你&#xff0c;会怎么做&#xff1f;前不久我还会觉得这好像太麻烦了&#xff0c;now,soeasy。 自动化测试工具&#xff1a;Selenium Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行…

若依前后端部署之后验证码不显示

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;&#x1f449;雄雄的小课堂&#x1f448;。 最近的几天都在搞publiccms的内容&#xff0c;从0到1实现&#xff0c;在花费了大量精力下&#xff0c;终于将一个门户站完完全全的实现了&#xff0c;且还可以…

汇编语言(六)之输出字符的前导后字符

输入一个字符&#xff0c;输出该字符的前导后字符 程序运行&#xff1a; 代码&#xff1a; datas segmenta db ?inputPrompt db input a lowercase character:$outputPrompt db 0dh,0ah,output …

若依部署上线之后验证码不显示的解决方法之一

“大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;????雄雄的小课堂????。”最近的几天都在搞publiccms的内容&#xff0c;从0到1实现&#xff0c;在花费了大量精力下&#xff0c;终于将一个门户站完完全全的实现了&#xff0c;且还可以自定义扩…