C#执行异步操作的几种方式比较和总结

原文出处: Durow(@Durow)   欢迎分享原创到伯乐头条

0×00 引言

之前写程序的时候在遇到一些比较花时间的操作例如HTTP请求时,总是会new一个Thread处理。对XxxxxAsync()之类的方法也没去了解过,倒也没遇到什么大问题。最近因为需求要求用DevExpress写界面,跑起来后发现比Native控件效率差好多。这才想到之前看到的“金科玉律”:不要在UI线程上执行界面无关的操作,因此集中看了下C#的异步操作,分享一下自己的比较和总结。

0×01 测试方法

IDE:VS2015 Community

.NET版本:4.5

使用函数随机休眠100到500毫秒来模拟耗时任务,并返回任务花费的时间,在UI线程上调用这个方法会造成阻塞,导致UI假死,因此需要通过异步方式执行这个任务,并在信息输出区域显示花费的时间。

主界面中通过各种不同按钮测试不同类型的异步操作

0×02 使用Thread进行异步操作

使用ThreadPool进行异步操作的方法如下所示,需要注意的就是IsBackground默认为false,也就是该线程对调用它的线程不产生依赖,当调用线程退出时该线程也不会结束。因此需要将IsBackground设置为true以指明该线程是后台线程,这样当主线程退出时该线程也会结束。另外跨线程操作UI还是要借助Dispatcher.BeginInvoke(),如果需要阻塞UI线程可以使用Dispatcher.Invoke()。

0×03 使用ThreadPool进行异步操作

ThreadPool(线程池)的出现主要就是为了提高线程的复用(类似的还有访问数据库的连接池)。线程的创建是开销比较大的行为,为了达到较好的交互体验,开发中可能会大量使用异步操作,特别是需要频繁进行大量的短时间的异步操作时,频繁创建和销毁线程会在造成很多资源的浪费。而通过在线程池中存放一些线程,当需要新建线程执行操作时就从线程池中取出一个已经存在的空闲线程使用,如果此时没有空闲线程,且线程池中的线程数未达到线程池上限,则新建一个线程,使用完成后再放回到线程池中。这样可以极大程度上省去线程创建的开销。线程池中线程的最小和最大数都可以指定,不过多数情况下无需指定,CLR有一套管理线程池的策略。ThreadPool的使用非常简单,代码如下所示。跨线程操作UI仍需借助Dispatcher。

0×04 使用Task进行异步操作

Task进行异步操作时也是从线程池中获取线程进行操作,不过支持的操作更加丰富一些。而且Task<T>可以支持返回值,通过Task的ContinueWith()可以在Task执行结束后将返回值传入以进行操作,但在ContinueWith中跨线程操作UI仍需借助Dispatcher。另外Task也可以直接使用静态方法Task.Run<T>()执行异步操作。

0×05 使用async/await进行异步操作

这个是C#5中的新特性,当遇到await时,会从线程池中取出一个线程异步执行await等待的操作,然后方法立即返回。等异步操作结束后回到await所在的地方接着往后执行。await需要等待async Task<T>类型的函数。详细的使用方法可参考相关资料,测试代码如下所示。异步结束后的会返回到调用线程,所以修改UI不需要Dispatcher。

也可以把TestTask包装成async方法,这样就可以使用上图中注释掉的两行代码进行处理。包装后的异步方法如下所示:

async/await也是从线程池中取线程,可实现线程复用,而且代码简洁容易阅读,异步操作返回后会自动返回调用线程,是执行异步操作的首选方式。而且虽然是C#5的新特性,但C#4可以通过下载升级包来支持async/await。

0×06 关于效率

以上尝试的方法除了直接使用Thread之外,其他几种都是直接或间接使用线程池来获取线程的。从理论上来分析,创建线程时要给线程分配栈空间,线程销毁时需要回收内存,创建线程也会增加CPU的工作。因此可以连续创建线程并记录消耗的时间来测试性能。测试代码如下所示:

当测试Thread时每次测试在连续创建线程时内存和CPU都会有个小突起,不过在线程结束后很快就会降下去,在我的电脑上连续创建100个线程大概花费120-130毫秒。如图所示:

测试结果:

使用基于线程池的方法创建线程时,有时第一次会稍慢一些,应该是线程池内线程不足,时间开销在0-15毫秒,第一次创建内存也会上升。后面再测试时时间开销为0毫秒,内存表现也很平稳,CPU开销分布比较平均。测试结果如图所示:

0×07 结论

在执行异步操作时应使用基于线程池的操作,从代码的简洁程度和可读性上优先使用async/await方式。对于较老的.NET版本可以使用Task或ThreadPool。符合以下情况的可以使用Thread:

1、线程创建后需要持续工作到主线程退出的。这种情况下就算使用线程池线程也不会归还,实现不了复用,可以使用Thread。

2、线程在主线程退出后仍需要执行的,这种情况使用线程池线程无法满足需求,需要使用Thread并制定IsBackground为false(默认)。

0×08 相关下载

测试程序代码在:https://github.com/durow/TestArea/tree/master/AsyncTest/AsyncTest

时间: 2024-10-14 14:47:10

C#执行异步操作的几种方式比较和总结的相关文章

Shell脚本中执行mysql的几种方式(转)

Shell脚本中执行mysql的几种方式(转) 对于自动化运维,诸如备份恢复之类的,DBA经常需要将SQL语句封装到shell脚本.本文描述了在Linux环境下mysql数据库中,shell脚本下调用sql语句的几种方法,供大家参考.对于脚本输出的结果美化,需要进一步完善和调整.以下为具体的示例及其方法. 1.将SQL语句直接嵌入到shell脚本文件中 复制代码 代码如下: --演示环境   [[email protected] ~]# more /etc/issue   CentOS rele

iOS动画开发之二——UIView动画执行的另一种方式

iOS动画开发之二--UIView动画执行的另一种方式 上一篇博客中介绍了UIView的一些常用动画,通过block块,我们可以很方便简洁的创建出动画效果:http://my.oschina.net/u/2340880/blog/484457,这篇博客再介绍一种更加传统的执行UIView的动画的方法. 这种方式相比如block的方式,显得要麻烦一些,apple官方也推荐我们使用带block的创建动画的方式,我们可以将编程重心更多的放在动画逻辑的实现上.使用begin和commit方式主要分为三个

Spring中使用quartz执行定时任务的两种方式

一, 继承spring封装Quartz类(org.springframework.scheduling.quartz.QuartzJobBean)方式 spring-mvc-quartz2.xml: <bean id="job2Trigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail">

一张图帮你记忆,Spring Boot 应用在启动阶段执行代码的几种方式

前言 有时候我们需要在应用启动时执行一些代码片段,这些片段可能是仅仅是为了记录 log,也可能是在启动时检查与安装证书 ,诸如上述业务要求我们可能会经常碰到 Spring Boot 提供了至少 5 种方式用于在应用启动时执行代码.我们应该如何选择?本文将会逐步解释与分析这几种不同方式 CommandLineRunner CommandLineRunner 是一个接口,通过实现它,我们可以在 Spring 应用成功启动之后 执行一些代码片段 @Slf4j @Component @Order(2)

【转】Python中执行cmd的三种方式

原文链接:http://blog.csdn.net/menglei8625/article/details/7494094 目前我使用到的python中执行cmd的方式有三种: 1. 使用os.system("cmd") 这是最简单的一种方法,特点是执行的时候程序会打出cmd在linux上执行的信息.使用前需要import os. [python] view plaincopyprint? os.system("ls") 2. 使用Popen模块产生新的proces

hibernate执行sql的三种方式

方式一:直接使用HibernateTemplate的find()方法,find方法支持执行hql语句 List<T> list = this.getHibernateTemplate().find(finalHql, params);  方式二:获取SessionFactory,再获取Session SessionFactory sf = this.getHibernateTemplate().getSessionFactory(); Session s = sf.getCurrentSess

python执行系统命令的四种方式

一.os模块 1. os.system('cmd') 在子终端运行系统命令,不能获取命令执行后的返回信息以及执行返回的状态 import os os.system('date') # 2016年 06月 30日 星期四 19:26:21 CST OS.system 2. os.popen(cmd) 不仅执行命令而且返回执行后的信息对象(常用于需要获取执行命令后的返回信息) ,读取结果是使用read方法,是阻塞模式,一旦读取到结果再次读取的时候返回内容为空. import os nowtime =

shell脚本执行的几种方式

执行shell脚本有以下几种方式 1.相对路径方式,需先cd到脚本路径下 [[email protected] tmp]# cd /tmp [[email protected] tmp]# ./ceshi.sh 脚本执行成功 2.绝对路径方式 [[email protected] tmp]# /tmp/ceshi.sh 脚本执行成功 3.bash命令调用 [[email protected] /]# bash /tmp/ceshi.sh 脚本执行成功 4.. (空格)  相对或绝对方式 [[em

操作系统,编程语言分类,执行python两种方式,变量,内存管理,定义变量的三个特征

操作系统 1.什么是操作系统 操作系统位于计算机硬件与应用软件之间 是一个协调.管理.控制计算机硬件资源与软件资源的控制程序 2.为何要有操作系统? 1.控制硬件 2.把对硬件的复杂的操作封装成优美简单的接口(文件),给用户或者应用程序去使用 注意:一套完整的计算机系统包含三部分 应用程序:qq,暴风影音,快播 操作系统:windows,linux,unix 计算机硬件 强调: 我们以后开发的都是应用程序 应用程序无法直接操作硬件,但凡要操作硬件,都是调用操作系统的接口 编程语言分类 1.机器语