欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

常见的异步方式async 和 await

程序员文章站 2022-07-09 12:54:53
之前研究过c#的async和await关键字,幕后干了什么,但是不知道为什么找不到相关资料了。现在重新研究一遍,顺便记录下来,方便以后查阅。基础知识async 关键字标注一个方法,该方法返回值是一个Task、或者Task、void、包含GetAwaiter方法的类型。该方法通常包含一个await表达... ......

之前研究过c#的async和await关键字,幕后干了什么,但是不知道为什么找不到相关资料了。现在重新研究一遍,顺便记录下来,方便以后查阅。

基础知识

async 关键字标注一个方法,该方法返回值是一个task、或者task<tresult>、void、包含getawaiter方法的类型。该方法通常包含一个await表达式。该表达式标注一个点,将被某个异步方法回跳到该点。并且,当前函数执行到该点,将立刻返回控制权给调用方。

以上描述了async方法想干的事情,至于如何实现,这里就不涉猎了。

个人见解

由此可以知道,async 和await关键字主要目的是为了控制异步线程的同步,让一个异步过程,表现得好像同步过程一样。

比如async 方法分n个任务去下载网页并进行处理:先await下载,然后立刻返回调用方,之后的处理就由异步线程完成下载后调用。这时候调用方可以继续执行它的任务,不过,如果调用方立刻就需要async的结果,那么应该就只能等待,不过大多数情况:他暂时不需要这个结果,那么就可以并行处理这些代码。

可见,并行性体现在await 上,如果await 点和最终的数据结果距离越远,那么并行度就越高。如果await的点越多,相信也会改善并行性。

资料显示,async 和await 关键字并不会创建线程,这是很关键的一点。他们只是创建了一个返回点,提供给需要他的线程使用。那么线程究竟是谁创建?注意await 表达式的组成,他需要一个task,一个task并不代表一定要创建线程,也可以是另一个async方法,但是层层包裹最里面的方法,很可能就是一个原生的task,比如await task.run(()=>thread.sleep(0)); ,这个真正产生线程的语句,就会根据前面那些await点,逐个回调。

从这点来看,async 方法,未必就是一个异步方法,他在语义上更加贴近“非阻塞”, 当遇到阻塞操作,立刻用await定点返回,至于其他更深一层的解决手段,它就不关心了。这是程序员需要关心的,程序员需要用真正的创建线程代码,来完成异步操作(当然这一步可由库程序员完成)。

注意async的几个返回值类型,这代表了不同的使用场景。如果是void,说明客户端不关心数据同步问题,它只需要线程的控制权立刻返回。可以用在ui 等场合,如果是task,客户端也不关心数据,但是它希望能够控制异步线程,这可能是对任务执行顺序有一定的要求。当然,最常见的是task<tresult>。

综上,async和await并不是为了多任务而设计的,如果追求高并发,应该在async函数内部用task好好设计一番。在使用async 和await的时候,只需要按照非阻塞的思路去编写代码就可以了,至于幕后怎么处理就交给真正的多线程代码创建者吧。

示范代码

        static async task runtaskasync(int step)
        {
            for(int i=0; i < step; i++)
            {
                await task.run(()=>thread.sleep(tmloop));//点是静态的,依次执行
                thread.sleep(tm2);
            }
            thread.sleep(tm3);
        }

//客户端
            task tk= runtaskasync(step);
            thread.sleep(tm1);//这一段是并行的,取max(函数,代码段)最大时间
            tk.wait( );//这里代表最终数据

为了达到高度并行,应该用真正的多线程代码:

        static async task runtaskbyparallelasync(int step)
        {
            await task.run(()=>parallel.for(0,step,
                s=>{loop(tmloop);
                    loop(tm2);
                    }
            ));
            loop(tm3);
        }

并行编码方法

并行执行有几个方法,第一个是创建n个task,一起启动。问题是怎么处理await点。每个task写一个await点是不行的,因为遇到第一个await就立刻返回,而不会开启所有任务并行执行。因此await不能随便放。那么如何为一组task设定await点呢?可以通过task.whenall 这个方法,他会等待一组task执行完毕返回。

特定情况下,可以用parallel.for 来开启一组任务,但是这个类并没有实现async模式,也就是它会阻塞当前线程,所以需要用一个task来包裹它。

可见,非阻塞和并行不完全是一回事。