3

.Net Core中无处不在的Async/Await是如何提升性能的?

 2 years ago
source link: https://www.cnblogs.com/wei325/p/15957951.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.



Async/Await在.Net Core中真的是无处不在,到处都是异步操作,那为什么要用?有什么作用?别人说能提升性能?网上一堆文章看的绕晕了也没说清楚,

所以这里从理论,实践,原理一个个解开这些疑问。

二、Async/Await有什么用?

1.Async/Await用法示例

用法很简单,这里就不详细说具体怎么用了,只提供一个示例,我们的目标是研究它的作用。

 public class AsyncAwaitTest
    {
        public void Start()
        {
            Console.WriteLine($"aaa,线程Id:{Thread.CurrentThread.ManagedThreadId}");
            AsyncMethod();
            Console.WriteLine($"eee,线程Id:{Thread.CurrentThread.ManagedThreadId}");
            Console.ReadLine();
        }
        public async Task<bool> AsyncMethod()
        {
            Console.WriteLine($"bbb,线程Id:{Thread.CurrentThread.ManagedThreadId}");
            await Task.Run(() => {

                Thread.Sleep(500);
                Console.WriteLine($"ccc,线程Id:{Thread.CurrentThread.ManagedThreadId}");
            });
            Console.WriteLine($"ddd,线程Id:{Thread.CurrentThread.ManagedThreadId}");
            return true;
        }
    }

2.async/await的特点

1)不会阻塞线程

从示例的执行顺序,可以看出,当执行async/await方法时,主线程遇到await关键字,主线程就返回执行“eee”,然后释放,而不是等待,新开了一个子线程6执行另外的业务,await前面的方法还是主线程执行,await后面的方法,等线程6执行完了再继续执行。

2)同步的方式写异步

虽然是用了异步,但还是等待执行结果再往下执行,执行流程是同步的。

3.async/await能提升性能吗?

这个应该是大家最关心的问题了。

能提升单个请求的性能吗?

答案是不能的。很明显看的到,await等待了结果再执行后面的逻辑,还是串行的,执行完该多少秒还是多少秒, 中间还切换线程去处理了,相比同步来说还多了切换线程的损耗。

那async/await的意义何在?

在于多请求并发处理,且资源有限的时候,能增加吞吐量(单位时间处理的请求),增加cpu的利用率。

简单说就是有10个线程,每个线程的速度没有提升,然后居然QPS能提升?!

先来看一段微软官网的描述

  此模型可很好地处理典型的服务器方案工作负荷。由于没有专用于阻止未完成任务的线程,因此服务器线程池可服务更多的Web请求。

  考虑使用两个服务器:一个运行异步代码,一个不运行异步代码。对于本例,每个服务器只有5个线程可用于服务器请求。此字数太小,不切实际,仅供演示。

  假设这两个服务器都接收6个并发请求。每个请求执行一个I/O操作。未运行异步代码的服务器必须对第6个请求排队,直到5个线程中的一个完成了I/O密集型工作

并编写了响应。此时收到了第20个请求,由于队列过长,服务器可能会开始变慢。

  运行有异步代码的服务器也需要对第6个请求排队,但由于使用了async和await,I/O密集型工作开始时,每个线程都会得到释放,无需等到工作结束。

收到第20个请求时,传入请求队列将变得很小(如果其中还有请求的话),且服务器不会慢。

  尽管这是一个人为想象的示例,但现实世界中其工作方式与此类似。事实上,相比服务器将线程专用于接收到的每个请求,使用async和await能够使

服务器处理一个数量级的请求。

  注意上面官网描述的I/O密集型。什么样的是I/O密集型呢?,就是cpu性能必硬盘内存好太多,大部分时间都是cpu在等IO的读写操作。例如读文件,读文件的时候是不需要cpu参与的,只需要发一个命令给硬盘,硬盘读完文件会再通知cpu继续处理,这种叫DMA技术

  DMA(Direct Memory Access,直接存储器访问) 是所有现代电脑的重要特色,它是指一种高速的数据传输操作,允许在外部设备和存储器之间直接读写数据,既不通过cpu,也不需要cpu干预。

  这个时候异步就显出它的优势来了,比如读文件需要1s,如果是同步操作,那个就有一个线程在等1s在往下执行。如果是异步的,读文件的时候,这个线程就释放了,等读完文件,硬盘通知cpu再

派一个线程接着处理,那中间的1秒,原来的线程就可以去处理其他请求了。

4.代码对照说明

public class HomeController : Controller
    {
        /// <summary>
        /// 同步请求
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public string GetData()
        {
            var result = System.IO.File.ReadAllBytes(@"F:\package\package.rar");
            return "ok";
        }

        /// <summary>
        /// 异步请求
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public async Task<string> GetDataAsync2()
        {
            var result = await System.IO.File.ReadAllBytesAsync(@"F:\package\package.rar");
            return "ok";
        }
    }

同步请求的流程为

 可以看出,硬盘在读取文件时,线程是在等待的,这时候线程1在这1s中是不工作的,空等状态。

异步请求的流程为

 异步请求时,线程1遇到await关键字,发出命令就返回,然后释放掉了,硬盘读完数据会通知cpu,这时cpu派一个新的线程去接着处理,

因此,读文件的这1s,线程1可以去处理其它请求了,没有空等,这就是提高了cpu的利用率,单位时间内处理的请求数就变大了。

cpu密集型的异步是不能提高QPS的,下面代码就是cpu密集型的。

cpu密集型:计算密集型,硬盘、内存性能比cpu好很多,或不太需要访问I/O设备。

     /// <summary>
        /// 异步请求
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public async Task<string> GetDataAsync2()
        {
            await Task.Run(() => {
                Thread.Sleep(100);//模拟业务处理耗时
            });
            return "ok";
        }

这里前面主线程遇到await虽然释放了,但await里面又有一个线程接着工作,cpu(线程并没有空闲)

I/O密集型中IO的操作有哪些呢?

文件读写、http请求、数据库请求、redis请求。。。等等。

 开发中哪些推荐用异步呢?

Web开发推荐、有Async的API的,Action、Filter、数据库访问、中间件等等。。。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK