C#执行异步操作的几种方式比较和总结
C#执行异步操作的几种方式比较和总结
0x00 引言
之前写程序的时候在遇到一些比较花时间的操作例如HTTP请求时,总是会new一个Thread处理。对XxxxxAsync()之类的方法也没去了解过,倒也没遇到什么大问题。最近因为需求要求用DevExpress写界面,跑起来后发现比Native控件效率差好多。这才想到之前看到的“金科玉律”:不要在UI线程上执行界面无关的操作,因此集中看了下C#的异步操作,分享一下自己的比较和总结。
0x01 测试方法
IDE:VS2015 Community
.NET版本:4.5
使用函数随机休眠100到500毫秒来模拟耗时任务,并返回任务花费的时间,在UI线程上调用这个方法会造成阻塞,导致UI假死,因此需要通过异步方式执行这个任务,并在信息输出区域显示花费的时间。

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

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

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

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

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

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

async/await也是从线程池中取线程,可实现线程复用,而且代码简洁容易阅读,异步操作返回后会自动返回调用线程,是执行异步操作的首选方式。而且虽然是C#5的新特性,但C#4可以通过下载升级包来支持async/await。
0x06 关于效率
以上尝试的方法除了直接使用Thread之外,其他几种都是直接或间接使用线程池来获取线程的。从理论上来分析,创建线程时要给线程分配栈空间,线程销毁时需要回收内存,创建线程也会增加CPU的工作。因此可以连续创建线程并记录消耗的时间来测试性能。测试代码如下所示:

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

测试结果:

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

0x07 结论
在执行异步操作时应使用基于线程池的操作,从代码的简洁程度和可读性上优先使用async/await方式。对于较老的.NET版本可以使用Task或ThreadPool。符合以下情况的可以使用Thread:
1、线程创建后需要持续工作到主线程退出的。这种情况下就算使用线程池线程也不会归还,实现不了复用,可以使用Thread。
2、线程在主线程退出后仍需要执行的,这种情况使用线程池线程无法满足需求,需要使用Thread并制定IsBackground为false(默认)。
0x08 相关下载
测试程序代码在:https://github.com/durow/TestArea/tree/master/AsyncTest/AsyncTest
C#执行异步操作的几种方式比较和总结的更多相关文章
- C#执行异步操作的几种方式比较和总结(转发:https://www.cnblogs.com/durow/p/4826653.html)
0x00 引言 之前写程序的时候在遇到一些比较花时间的操作例如HTTP请求时,总是会new一个Thread处理.对XxxxxAsync()之类的方法也没去了解过,倒也没遇到什么大问题.最近因为需求要求 ...
- Shell脚本中执行mysql的几种方式(转)
Shell脚本中执行mysql的几种方式(转) 对于自动化运维,诸如备份恢复之类的,DBA经常需要将SQL语句封装到shell脚本.本文描述了在Linux环境下mysql数据库中,shell脚本下调用 ...
- java Web 启动时自动执行代码的几种方式
Web容器启动后执行代码的几种方式,其执行顺序为:4===>5===>1===>2===>3,即指定init-method的Bean开始执行,接着实现spring的Bean后置 ...
- 转 mysql mysql命令行中执行sql的几种方式总结
https://www.jb51.net/article/96394.htm 1.直接输入sql执行 MySQL> select now(); +---------------------+ | ...
- linux 中的命令是什么?执行命令的几种方式?如何自己创建命令?
linux 中的命令是什么? 命令是可执行的二进制程序 执行命令的几种方式? ./test.sh #相对路径执行 /data/test.sh ...
- Centos上执行Shell的四种方式
注意:我这里说的shell脚本是Bash Shell,其他类型的shell脚本不保证有效 1,方式一:进入shell文件所在目录 ./my.sh执行 ./my.sh ./的意思是说在当前的工作目录下执 ...
- web项目启动时,自动执行代码的几种方式
在项目开发过程中,往往需要一些功能随着项目启动而优先启动,下面我总结几种方式(非spring boot) spring boot的参考 spring boot 学习之路9 (项目启动后就执行特定方法) ...
- bat批处理执行python 的几种方式
第一种方式:@echo off C: cd C:\Users\administrator\Desktopstart python apidemo.py exit第二种方式: start cmd /K ...
- 一张图帮你记忆,Spring Boot 应用在启动阶段执行代码的几种方式
前言 有时候我们需要在应用启动时执行一些代码片段,这些片段可能是仅仅是为了记录 log,也可能是在启动时检查与安装证书 ,诸如上述业务要求我们可能会经常碰到 Spring Boot 提供了至少 5 种 ...
随机推荐
- Fis3的前端工程化之路[三大特性篇之资源定位]
Fis3版本:v3.4.22 Fis3的三大特性 资源定位:获取任何开发中所使用资源的线上路径 内容嵌入:把一个文件的内容(文本)或者base64编码(图片)嵌入到另一个文件中 依赖声明:在一个文本文 ...
- ADO.NET对象的详解
1. Connection 类 和数据库交互,必须连接它.连接帮助指明数据库服务器.数据库名字.用户名.密码,和连接数据库所需要的其它参数.Connection对象会被Command对象使用,这样就能 ...
- Js 变量声明提升和函数声明提升
Js代码分为两个阶段:编译阶段和执行阶段 Js代码的编译阶段会找到所有的声明,并用合适的作用域将它们关联起来,这是词法作用域的核心内容 包括变量声明(var a)和函数声明(function a(){ ...
- 深究标准IO的缓存
前言 在最近看了APUE的标准IO部分之后感觉对标准IO的缓存太模糊,没有搞明白,APUE中关于缓存的部分一笔带过,没有深究缓存的实现原理,这样一本被吹上天的书为什么不讲透彻呢?今天早上爬起来赶紧找了 ...
- Vertica 数据库知识汇总篇
Vertica 数据库知识汇总篇(更新中..) 1.Vertica 集群软件部署,各节点硬件性能测试 2.Vertica 创建数据库,创建业务用户测试 3.Vertica 数据库参数调整,资源池分配 ...
- GJM : C#设计模式汇总整理——导航 【原创】
感谢您的阅读.喜欢的.有用的就请大哥大嫂们高抬贵手"推荐一下"吧!你的精神支持是博主强大的写作动力以及转载收藏动力.欢迎转载! 版权声明:本文原创发表于 [请点击连接前往] ,未经 ...
- python select网络编程详细介绍
刚看了反应堆模式的原理,特意复习了socket编程,本文主要介绍python的基本socket使用和select使用,主要用于了解socket通信过程 一.socket模块 socket - Low- ...
- 清除打印机队列中无法清除的任务 & 清空打印池
故障现象典型表现为以下两种情况 1.当打印任务开始进行时,这些打印任务便被保存在打印作业列表(也称打印队列)内.如果打印机因意外暂停(如打印机未连接)而未完成打印任务,则该打印任务将列入打印队列,并且 ...
- 一步步学习javascript基础篇(9):ajax请求的回退
需求1: ajax异步请求 url标识请求参数(也就是说复制url在新页面打开也会是ajax后的效果) ajax异步请求没问题,问题一般出在刷新url后请求的数据没了,这就是因为url没有记录参数.如 ...
- 猫哥网络编程系列:详解 BAT 面试题
从产品上线前的接口开发和调试,到上线后的 bug 定位.性能优化,网络编程知识贯穿着一个互联网产品的整个生命周期.不论你是前后端的开发岗位,还是 SQA.运维等其他技术岗位,掌握网络编程知识均是岗位的 ...