当前位置:首页 > 技术分析 > 正文内容

Net Core中无处不在的Async/Await

ruisui884个月前 (02-14)技术分析22

一、简介

Async/Await在.Net Core中真的是无处不在,到处都是异步操作,那为什么要用?有什么作用?

二、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 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;
        }
    }

特点:

1)不会阻塞线程

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

2)同步的方式写异步

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


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

  此模型可很好地处理典型的服务器方案工作负荷。由于没有专用于阻止未完成任务的线程,因此服务器线程池可服务更多的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
    {
        /// 
        /// 同步请求
        /// 
        /// 
        /// 
        public string GetData()
        {
            var result = System.IO.File.ReadAllBytes(@"F:\package\package.rar");
            return "ok";
        }

        /// 
        /// 异步请求
        /// 
        /// 
        /// 
        public async Task 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设备。

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

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

Task异常捕获的方式

当Task运行中出现了异常,正常情况下我们在主线程的Try是捕获不到的,而如果在Task内部写try,出现了异常我们会完全不知道。下面就来介绍几个主线程捕获Task异常的方法。

阻塞线程式

我们可以使用Wait(),WaitAny(),WaitAll()来捕获Task的异常,详见下图:

捕获Task异常,准确来说要用AggregateException类,右边是运行结果,成功捕获到了异常信息,其它两个等待也是类似的用法,

在等待多个Task异常时,可以访问异常对象的InnerExceptions属性来遍历所有的异常:

上述异常捕获的解决方案,因为涉及到了等待,所以会阻塞主线程,并且如果异常发生在等待之前,同样是不能捕获到,所以这种方式,虽然简单,但是使用场景并不多。


异步式

我们知道Task有个ContinueWith方法,它会在Task完成后继续异步执行传入的委托,我们可以通过这个方法实现异常捕获,请看如下代码:

因为是异步执行,所以这样不会阻塞主线程。

事件式

事件式的思路是在主线程中定义事件,在Task中通过触发事件的形式让主线程捕获到异常,请看代码:

internal class TaskExceptionEventArgs:EventArgs
{
    /// 
    /// 存放Task引发的异常对象
    /// 
    public AggregateException AggregateException { get; set; }  
}

主代码如下:

class Program
{
    private static event EventHandler taskExceptionEventHandler;
    static void Main(string[] args)
    {
        //为事件添加事件处理器
        taskExceptionEventHandler = (sender, aeArgs) =>
        {
            Console.WriteLine(aeArgs.AggregateException.Message);
        };
        Task.Run(async () =>
        {
            await Task.Delay(2 * 1000);
            try
            {
                throw new AggregateException("内部异常1");
            }
            catch (AggregateException ex)
            {
                //触发事件,并传入参数
                taskExceptionEventHandler.Invoke(null, new TaskExceptionEventArgs
                {
                    AggregateException = ex
                });
            }
        });
    }
}

这样用法很灵活,而且拿到的是最直接的异常对象,并且不用等待Task执行完毕。

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/1969.html

标签: aysnc
分享给朋友:

“Net Core中无处不在的Async/Await” 的相关文章

vue中组件之间的通信方式

** 1.1 父子组件**a. 父向子传数据: 第1种: 父通过属性传值,子组件通过props接收数据(注:props传过来的数据是单向的,不可以进行修改)第2种:子组件可以通过$parent来获取父组件里的数据和调用父组件的方法(注:数据是双向的,还要注意如用了UI组件并且在该UI组件里重新定义一...

学会使用Vue JSX,一车老干妈都是你的

作者:子君转发链接:https://mp.weixin.qq.com/s/eAOivpHeowLShfwPfW8-BA?君自前端来,应知前端事。需求时时变,bug改不完。?连续几篇文章,每篇都有女神,被老铁给吐槽了,今天不提了女神了,反正女神都是别人的(扎心了)。这两天小编看了腾讯与老干妈的事情,晚...

neovim 0.9在win下配置 python开发环境

初级的一些配置点击下面链接查看neovim安装插件管理器neovim常用快捷键neovim python开发环境简易配置方法 (需要手动键入命令行 运行python)安装neovim python的模块pip install pynvim pip install jedi pip install n...

K8S NFS 共享存储

NFS 共享存储前面我们学习了 hostPath 与 Local PV 两种本地存储方式,但是平时我们的应用更多的是无状态服务,可能会同时发布在不同的节点上,这个时候本地存储就不适用了,往往就需要使用到共享存储了,比如最简单常用的网络共享存储 NFS,本节课我们就来介绍下如何在 Kubernetes...

「干货」FPGA设计中深度约束技巧及调试经验总结

今天跟大家分享的内容很重要,也是我们调试FPGA经验的总结。随着FPGA对时序和性能的要求越来越高,高频率、大位宽的设计越来越多。在调试这些FPGA样机时,需要从写代码时就要小心谨慎,否则写出来的代码可能无法满足时序要求。另外,最近跟网友聊天时,有谈到公众号寿命的问题,我觉得网络交换FPGA公众号应...

多项修正 尼康D4s发布最新1.10版固件

尼康公司与2014年8月27日发布了D4s的最新固件,固件版本号为C:1.10。这次固件升级,主要解决了一些BUG,并且对拍摄菜单与相机操作做了一定调整。下面是本次新固件的具体信息:尼康发布D4s最新C固件 1.10版对C固件升级到1.10版所作的修改:当选定运动VR模式并换上 AF-S 尼克尔 4...