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

聊聊多线程那一些事儿 之 四 经典应用(取与舍、动态创建)

程序员文章站 2022-07-28 15:46:41
前面已经讲解了task的运行、阻塞、同步、延续操作、取消等!今天我们就专门来聊聊关于async/await的那一些事,分析其实现原理,通过该文章你也该对async的使用还有更加清晰的理解 ......

   hello task,咱们又见面啦!!是不是觉得很熟读的开场白,哈哈你哟这感觉那就对了,说明你已经阅读过了我总结的前面4篇关于task的文章,谢谢支持!感觉不熟悉的也没有关系,在文章末尾我会列出前四篇文章的地址,可以点击详细阅读。

    前几篇文章分享了以后,无论是公众号还是博客园,都有小伙伴问我async/await的专栏总结分享,既然这样,那今天我们就专门来聊聊关于async/await的那一些事,通过该文章你也该对async的使用还有更加清晰的理解,谢谢!

async/await入门:

    async也就是我们说的异步方法,不废话,也不先说那么多的大理论,先上一个简单的实例,通过这个简单的实例实现和asyncd 初相识!!

 

 static void main(string[] args)
 {
     console.writeline($"主线程开始,线程id:{thread.currentthread.managedthreadid}\n");

     // 同步实现 
     addsync(1, 2);

     // 异步方法,没有 await
     addnoawaitsynchas(1, 2);

     // 异步方法,有 await
     addhasawaitasync(1, 2);

     console.writeline($"主线程结束,线程id:{thread.currentthread.managedthreadid}\n");
     console.readline();
     return;
 }

 /// <summary>
 /// 同步计算两个数字之和
 /// </summary>
 /// <param name="num1">参数1</param>
 /// <param name="num2">参数2</param>
 /// <returns></returns>
 private static int addsync(int num1, int num2)
 {
     thread.sleep(1000);
     console.writeline($"同步方法,线程id:{thread.currentthread.managedthreadid}\n");
     return num1 + num2;
 }

 /// <summary>
 /// 对两个数字求和 (异步方法,没有 await)
 /// </summary>
 /// <param name="num1">参数1</param>
 /// <param name="num2">参数2</param>
 /// <returns>结果</returns>
 private static async task<int> addnoawaitsynchas(int num1, int num2)
 {
     console.writeline($"异步线程没有await前,线程id:{thread.currentthread.managedthreadid}\n");
     // 两个数字求和,假设其中会涉及到很耗时的逻辑
     thread.sleep(1000);
     console.writeline($"异步线程没有await后,线程id:{thread.currentthread.managedthreadid}\n");
     return num1 + num2;
 }

 /// <summary>
 /// 对两个数字求和 (异步方法,有 await)
 /// </summary>
 /// <param name="num1">参数1</param>
 /// <param name="num2">参数2</param>
 /// <returns>结果</returns>
 private static async task<int> addhasawaitasync(int num1, int num2)
 {
     console.writeline($"异步线程await前,线程id:{thread.currentthread.managedthreadid}\n");
     // 两个数字求和,假设其中会涉及到很耗时的逻辑
     var add = add(num1, num2);
     int result = await add;
     console.writeline($"异步线程await后,线程id:{thread.currentthread.managedthreadid}\n");
     return result;
 }

 /// <summary>
 /// task 对两个数字求和
 /// </summary>
 /// <param name="num1">参数1</param>
 /// <param name="num2">参数2</param>
 /// <returns>结果</returns>
 private static task<int> add(int num1, int num2)
 {
     // 假设该逻辑执行起来很耗时
     var task = task.run(() =>
     {
         console.writeline($"我是task内部执行开始:线程id :{thread.currentthread.managedthreadid}\n");
         thread.sleep(5000);
         console.writeline($"我是task内部执行结束:线程id :{thread.currentthread.managedthreadid}\n");
         return num1 + num2;
     });

     return task;
 }

执行结果:

聊聊多线程那一些事儿 之 四 经典应用(取与舍、动态创建)

 

结合代码和执行结果,我们分析可以得出以下一些结论:

   1、通过async的写法和同步方法在实现和调用上都很相似

   2、异步方法async如果没有await关键词,其整体执行都是在主线中运行

    ----同步调用

    3、异步方法async有await关键词,其线程执行分水岭就在await

     ----await前,async执行还是在主线中执行

     ----await后,async的执行逻辑会新开一个线程

     ----也就是说,async其真正的异步还是await实现

     ​----而await修饰的实际是一个task修饰的变量或者返回的类型为task的方法体

     ​----所以最后的最后,async的异步还是通过task来实现的

    4、await是不能单独使用,一定是在是和async成对使用

     ----当然aysnc修饰的方法可以没有await关键词

  通过上面的一个简单实例,是不是发现要实现一个异步方法,是不是so easy,是的 ,你没说错,就是那么简单,但是也许你会问,干嘛实现一个异步方法整的的如此复杂,创建了这么多方法,是的,不急不急,我这样写,是为了更加清晰的明白其执行流程。好了,下面我们在一起来探讨一下aysnc/await的组成结构吧!

 

aysnc/await的组成结构:

 

其实异步方法的整体结构和一个普通的方法没有多大区别,唯一不一样的点,就是多了一个task逻辑主体,下面简单的分别来概要说明一下每一个环节:

聊聊多线程那一些事儿 之 四 经典应用(取与舍、动态创建)

    上面的图简单的绘制了一个异步方法在整体执行时的一个执行顺序。

异步方法调用

    个人觉得这个没有什么说的,其实很普通方法调用一样,只是说异步方法的调用结果一般为一个task对象,那么需要获获取其执行结果的值,或者对执行结果需要做一些逻辑处理,这个和操作一个普通的task一样,这儿就不在细说,不清楚的可以看我前面分享的几篇文章,会有详细的说明,谢谢!

aysnc的方法体

    通过实例我们应该已经知道,其实异步方法,也就是在普通的方法体上,加了一个async修饰罢了,其简单的结构大概是

    private aysnc task myaysnc(){具体方法实现}

    说说aysnc的返回类型

    其返回类型有三种情况,每一种情况适用于不同的业务场景,如下:

    a、tsak:其主要适用场景是,主程序只关心异步方法执行状态,不需要和主线程有任何执行结果数据交互。

    b、task<t>:其主要适用场景是,主程序不仅仅关心异步方法执行状态,并且还希望执行后返回一个数据类型为t的结果

    c、void: 主程序既不关系异步方法执行状态,也不关心其执行结果,只是主程序调用一次异步方法,对于除事件处理程序以外的代码,通常不鼓励使用 async void 方法,因为调用方不能

task逻辑主体

    aysnc为了实现异步,其中最关键的一个点就是await修饰符,await修饰的也就是task实现逻辑主体。task实现逻辑主体,其实在上就是一个task实例,所以其里面的实例逻辑使用和一个普通的task实例定义操作都是一样的,在此也就不在详细说明,前面的几篇文章也有详细的说明了,如果不清楚的可以查看以前的几篇文章。

     

aysnc/await的原理分析:

 

    在说这一块之前,我们先把写的代码编译后,在通过反编译后发现在代码里面根本找不到aysnc/await关键词,有兴趣的小伙伴,你也可以这样操作分析一下。那么我们就明白了aysnc/await其实是编译器层面给的一个语法糖,是为了方便实现一个异步方罢了。

从反编译后的代码看出编译器新生成一个继承iasyncstatemachine 的状态机结构asyncd(代码中叫<addhasawaitasync>d__2),下面是基于反编译后的代码来分析的。

iasyncstatemachine最基本的状态机接口定义:

public interface iasyncstatemachine {       
 void movenext();       
 void setstatemachine(iasyncstatemachine statemachine); 
}

 

    好了,说道这儿我们已经知道aysnc/await是编程器层面的一个语法糖,那么我们在来分析一下其执行的流程如下:

    第一步:主线程调用 addhasawaitasync(1,2)异步方法

   第二步:addhasawaitasync()方法内初始化状态机状态为-1,启动<addhasawaitasync>d__2

    第三步:movenext方法内部开始执行,task.run实现了把业务逻辑执行丢到线程池中,返回一个可等待的任务句柄。其底层还是借助委托实现。

    第四步:到此程序以及开启了两个线程,一个主线程,一个task线程,两个线程相互独立互不阻塞,各自执行对应的业务逻辑。

    好了,时间不早了,就先到这儿吧,感觉这一篇文章总结的不怎么好,先这样,后续我们在持续交流,谢谢!

 

猜您喜欢: 

 第一篇:聊聊多线程哪一些事儿(task)之 一创建运行与阻塞

 第二篇:聊聊多线程哪一些事儿(task)之 二 延续操作

 第三篇:聊聊多线程那一些事儿(task)之 三 异步取消和异步方法

 第四篇:聊聊多线程那一些事儿 之 四 经典应用(取与舍、动态创建)

end
为了更高的交流,欢迎大家关注我的公众号,扫描下面二维码即可关注,谢谢:

聊聊多线程那一些事儿 之 四 经典应用(取与舍、动态创建)