详解C#中 Thread,Task,Async/Await,IAsyncResult的那些事儿
说起异步,Thread,Task,async/await,IAsyncResult 这些东西肯定是绕不开的,今天就来依次聊聊他们
1.线程(Thread)
多线程的意义在于一个应用程序中,有多个执行部分可以同时执行;对于比较耗时的操作(例如io,数据库操作),或者等待响应(如WCF通信)的操作,可以单独开启后台线程来执行,这样主线程就不会阻塞,可以继续往下执行;等到后台线程执行完毕,再通知主线程,然后做出对应操作!
在C#中开启新线程比较简单
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
static void Main(string[] args){ Console.WriteLine("主线程开始"); //IsBackground=true,将其设置为后台线程 Thread t = new Thread(Run) { IsBackground = true }; t.Start(); Console.WriteLine("主线程在做其他的事!"); //主线程结束,后台线程会自动结束,不管有没有执行完成 //Thread.Sleep(300); Thread.Sleep(1500); Console.WriteLine("主线程结束");}static void Run(){ Thread.Sleep(700); Console.WriteLine("这是后台线程调用");} |
执行结果如下图,

可以看到在启动后台线程之后,主线程继续往下执行了,并没有等到后台线程执行完之后。
1.1 线程池
试想一下,如果有大量的任务需要处理,例如网站后台对于HTTP请求的处理,那是不是要对每一个请求创建一个后台线程呢?显然不合适,这会占用大量内存,而且频繁地创建的过程也会严重影响速度,那怎么办呢?线程池就是为了解决这一问题,把创建的线程存起来,形成一个线程池(里面有多个线程),当要处理任务时,若线程池中有空闲线程(前一个任务执行完成后,线程不会被回收,会被设置为空闲状态),则直接调用线程池中的线程执行(例asp.net处理机制中的Application对象),
使用事例:
|
1
2
3
4
5
6
7
8
|
for (int i = 0; i < 10; i++){ ThreadPool.QueueUserWorkItem(m => { Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString()); });}Console.Read(); |
运行结果:

可以看到,虽然执行了10次,但并没有创建10个线程。
1.2 信号量(Semaphore)
Semaphore负责协调线程,可以限制对某一资源访问的线程数量
这里对SemaphoreSlim类的用法做一个简单的事例:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
static SemaphoreSlim semLim = new SemaphoreSlim(3); //3表示最多只能有三个线程同时访问static void Main(string[] args){ for (int i = 0; i < 10; i++) { new Thread(SemaphoreTest).Start(); } Console.Read();}static void SemaphoreTest(){ semLim.Wait(); Console.WriteLine("线程" + Thread.CurrentThread.ManagedThreadId.ToString() + "开始执行"); Thread.Sleep(2000); Console.WriteLine("线程" + Thread.CurrentThread.ManagedThreadId.ToString() + "执行完毕"); semLim.Release();} |
执行结果如下:


可以看到,刚开始只有三个线程在执行,当一个线程执行完毕并释放之后,才会有新的线程来执行方法!
除了SemaphoreSlim类,还可以使用Semaphore类,感觉更加灵活,感兴趣的话可以搜一下,这里就不做演示了!
2.Task
Task是.NET4.0加入的,跟线程池ThreadPool的功能类似,用Task开启新任务时,会从线程池中调用线程,而Thread每次实例化都会创建一个新的线程。
|
1
2
3
4
5
6
7
8
9
10
11
|
Console.WriteLine("主线程启动");//Task.Run启动一个线程//Task启动的是后台线程,要在主线程中等待后台线程执行完毕,可以调用Wait方法//Task task = Task.Factory.StartNew(() => { Thread.Sleep(1500); Console.WriteLine("task启动"); });Task task = Task.Run(() => { Thread.Sleep(1500); Console.WriteLine("task启动");});Thread.Sleep(300);task.Wait();Console.WriteLine("主线程结束"); |
执行结果如下:

开启新任务的方法:Task.Run()或者Task.Factory.StartNew(),开启的是后台线程
要在主线程中等待后台线程执行完毕,可以使用Wait方法(会以同步的方式来执行)。不用Wait则会以异步的方式来执行。
比较一下Task和Thread:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
static void Main(string[] args){ for (int i = 0; i < 5; i++) { new Thread(Run1).Start(); } for (int i = 0; i < 5; i++) { Task.Run(() => { Run2(); }); }}static void Run1(){ Console.WriteLine("Thread Id =" + Thread.CurrentThread.ManagedThreadId);}static void Run2(){ Console.WriteLine("Task调用的Thread Id =" + Thread.CurrentThread.ManagedThreadId);} |
执行结果:

可以看出来,直接用Thread会开启5个线程,用Task(用了线程池)开启了3个!
2.1 Task<TResult>
Task<TResult>就是有返回值的Task,TResult就是返回值类型。
|
1
2
3
4
5
6
7
8
9
|
Console.WriteLine("主线程开始");//返回值类型为stringTask<string> task = Task<string>.Run(() => { Thread.Sleep(2000); return Thread.CurrentThread.ManagedThreadId.ToString(); });//会等到task执行完毕才会输出;Console.WriteLine(task.Result);Console.WriteLine("主线程结束"); |
运行结果:

通过task.Result可以取到返回值,若取值的时候,后台线程还没执行完,则会等待其执行完毕!
简单提一下:
Task任务可以通过CancellationTokenSource类来取消,感觉用得不多,用法比较简单,感兴趣的话可以搜一下!
3. async/await
async/await是C#5.0中推出的,先上用法:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
static void Main(string[] args){ Console.WriteLine("-------主线程启动-------"); Task<int> task = GetStrLengthAsync(); Console.WriteLine("主线程继续执行"); Console.WriteLine("Task返回的值" + task.Result); Console.WriteLine("-------主线程结束-------");}static async Task<int> GetStrLengthAsync(){ Console.WriteLine("GetStrLengthAsync方法开始执行"); //此处返回的<string>中的字符串类型,而不是Task<string> string str = await GetString(); Console.WriteLine("GetStrLengthAsync方法执行结束"); return str.Length;}static Task<string> GetString(){ //Console.WriteLine("GetString方法开始执行") return Task<string>.Run(() => { Thread.Sleep(2000); return "GetString的返回值"; });} |
async用来修饰方法,表明这个方法是异步的,声明的方法的返回类型必须为:void,Task或Task<TResult>。
await必须用来修饰Task或Task<TResult>,而且只能出现在已经用async关键字修饰的异步方法中。通常情况下,async/await成对出现才有意义,
看看运行结果:

可以看出来,main函数调用GetStrLengthAsync方法后,在await之前,都是同步执行的,直到遇到await关键字,main函数才返回继续执行。
那么是否是在遇到await关键字的时候程序自动开启了一个后台线程去执行GetString方法呢?
现在把GetString方法中的那行注释加上,运行的结果是:

大家可以看到,在遇到await关键字后,没有继续执行GetStrLengthAsync方法后面的操作,也没有马上反回到main函数中,而是执行了GetString的第一行,以此可以判断await这里并没有开启新的线程去执行GetString方法,而是以同步的方式让GetString方法执行,等到执行到GetString方法中的Task<string>.Run()的时候才由Task开启了后台线程!
那么await的作用是什么呢?
可以从字面上理解,上面提到task.wait可以让主线程等待后台线程执行完毕,await和wait类似,同样是等待,等待Task<string>.Run()开始的后台线程执行完毕,不同的是await不会阻塞主线程,只会让GetStrLengthAsync方法暂停执行。
那么await是怎么做到的呢?有没有开启新线程去等待?

只有两个线程(主线程和Task开启的线程)!至于怎么做到的(我也不知道......>_<),大家有兴趣的话研究下吧!
4.IAsyncResult
IAsyncResult自.NET1.1起就有了,包含可异步操作的方法的类需要实现它,Task类就实现了该接口

在不借助于Task的情况下怎么实现异步呢?
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
class Program{ static void Main(string[] args) { Console.WriteLine("主程序开始--------------------"); int threadId; AsyncDemo ad = new AsyncDemo(); AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod); IAsyncResult result = caller.BeginInvoke(3000,out threadId, null, null); Thread.Sleep(0); Console.WriteLine("主线程线程 {0} 正在运行.",Thread.CurrentThread.ManagedThreadId) //会阻塞线程,直到后台线程执行完毕之后,才会往下执行 result.AsyncWaitHandle.WaitOne(); Console.WriteLine("主程序在做一些事情!!!"); //获取异步执行的结果 string returnValue = caller.EndInvoke(out threadId, result); //释放资源 result.AsyncWaitHandle.Close(); Console.WriteLine("主程序结束--------------------"); Console.Read(); }}public class AsyncDemo{ //供后台线程执行的方法 public string TestMethod(int callDuration, out int threadId) { Console.WriteLine("测试方法开始执行."); Thread.Sleep(callDuration); threadId = Thread.CurrentThread.ManagedThreadId; return String.Format("测试方法执行的时间 {0}.", callDuration.ToString()); }}public delegate string AsyncMethodCaller(int callDuration, out int threadId); |
关键步骤就是红色字体的部分,运行结果:

和Task的用法差异不是很大!result.AsyncWaitHandle.WaitOne()就类似Task的Wait。
5.Parallel
最后说一下在循环中开启多线程的简单方法:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
Stopwatch watch1 = new Stopwatch();watch1.Start();for (int i = 1; i <= 10; i++){ Console.Write(i + ","); Thread.Sleep(1000);}watch1.Stop();Console.WriteLine(watch1.Elapsed);Stopwatch watch2 = new Stopwatch();watch2.Start();//会调用线程池中的线程Parallel.For(1, 11, i =>{ Console.WriteLine(i + ",线程ID:" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(1000);});watch2.Stop();Console.WriteLine(watch2.Elapsed); |
运行结果:

循环List<T>:
|
1
2
3
4
5
6
|
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6, 6, 7, 8, 9 };Parallel.ForEach<int>(list, n =>{ Console.WriteLine(n); Thread.Sleep(1000);}); |
执行Action[]数组里面的方法:
|
1
2
3
4
5
6
7
8
9
|
Action[] actions = new Action[] { new Action(()=>{ Console.WriteLine("方法1"); }), new Action(()=>{ Console.WriteLine("方法2"); })};Parallel.Invoke(actions); |
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持脚本之家!
详解C#中 Thread,Task,Async/Await,IAsyncResult的那些事儿的更多相关文章
- 现代JS中的流程控制:详解Callbacks 、Promises 、Async/Await
JavaScript经常声称是_异步_.那是什么意思?它如何影响发展?近年来这种方法有何变化? 请思考以下代码: result1 = doSomething1(); result2 = doSomet ...
- C#异步中的Task,async,await
class Program { static void Main(string[] args) { Console.WriteLine("我是主线程,线程ID:{0}", Thre ...
- 详解Android中的四大组件之一:Activity详解
activity的生命周期 activity的四种状态 running:正在运行,处于活动状态,用户可以点击屏幕,是将activity处于栈顶的状态. paused:暂停,处于失去焦点的时候,处于pa ...
- jQuery:详解jQuery中的事件(二)
上一篇讲到jQuery中的事件,深入学习了加载DOM和事件绑定的相关知识,这篇主要深入讨论jQuery事件中的合成事件.事件冒泡和事件移除等内容. 接上篇jQuery:详解jQuery中的事件(一) ...
- 图文详解Unity3D中Material的Tiling和Offset是怎么回事
图文详解Unity3D中Material的Tiling和Offset是怎么回事 Tiling和Offset概述 Tiling表示UV坐标的缩放倍数,Offset表示UV坐标的起始位置. 这样说当然是隔 ...
- 【转】详解C#中的反射
原帖链接点这里:详解C#中的反射 反射(Reflection) 2008年01月02日 星期三 11:21 两个现实中的例子: 1.B超:大家体检的时候大概都做过B超吧,B超可以透过肚皮探测到你内 ...
- 详解Webwork中Action 调用的方法
详解Webwork中Action 调用的方法 从三方面介绍webwork action调用相关知识: 1.Webwork 获取和包装 web 参数 2.这部分框架类关系 3.DefaultAction ...
- 【转】详解JavaScript中的this
ref:http://blog.jobbole.com/39305/ 来源:foocoder 详解JavaScript中的this JavaScript中的this总是让人迷惑,应该是js众所周知的坑 ...
- 深入详解SQL中的Null
深入详解SQL中的Null NULL 在计算机和编程世界中表示的是未知,不确定.虽然中文翻译为 “空”, 但此空(null)非彼空(empty). Null表示的是一种未知状态,未来状态,比如小明兜里 ...
随机推荐
- Python单元测试框架unittest之单用例管理(一)
一.概述 本文介绍python的单元测试框架unittest,unittest原名为PyUnit,是由java的JUnit衍生而来,这是Python自带的标准模块unittest.unittest是基 ...
- WIN10家庭版 访问WINXP 共享打印机
WIN10家庭版 1.安装对应的打印机驱动 2.打开WIN10计算机---在地址栏中输入:\\计算机XP名称,显示对应的共享资源,直接选择即可.如果无法访问则进行如下第三步 3.设置过程 开始 -设置 ...
- C语言:虚拟地址 和编译模式
所谓虚拟地址空间,就是程序可以使用的虚拟地址的有效范围.虚拟地址和物理地址的映射关系由操作系统决定,相应地,虚拟地址空间的大小也由操作系统决定,但还会受到编译模式的影响.这节我们先讲解CPU,再讲解编 ...
- JAVA基础之JDK、JRE、JVM关系
什么是JRE和JDK JDK(Java Development Kit Java开发工具包) JDK是提供给Java开发人员使用的,其中包含了java的开发工具,也包括了JRE.所以安装了JDK,就不 ...
- Python开发篇——构建虚拟Python开发环境(Conda+Poetry)
前言 之前虽略有提及Python,但是没有实际地写点料.惭愧,惭愧,所以这次先起个头,讲讲如何构建虚拟Python开发环境.相信之前看过我博客的人可能会想:博主不会又要聊聊Docker吧?放心,不会. ...
- Qt 5.2中编译加载MySQL数据库驱动问题的总结
背景: 本科毕业设计涉及图形界面与数据库查询.选择使用Qt实现图形界面编程,使用MySQL构建数据库.之前安装了Qt 5.2,后来又安装了MySQL Server 5.6 (FULL完全安装).接着就 ...
- videojs文档翻译-EventTarget
EventTarget new EventTarget() EventTarget是一个可以与DOM EventTarget具有相同API的类. 它增加了包含冗长功能的缩写功能. 例如:on函数是 ...
- linux下利用JMX监控Tomcat
利用JMX监控Tomcat,就是相当于部署在tomcat上的应用作为服务端,也就是被管理资源的对象.然后通过程序或者jconsole远程连接到该应用上来.远程连接需要服务器端提供ip和port.如果需 ...
- JVM的内存管理机制-转载
JVM的内存管理机制 一.JVM的内存区域 对于C.C++程序员来说,在内存管理领域,他们既拥有每一个对象的"所有权",又担负着每一个对象生命开始到终结的维护责任. 对Java程序 ...
- NAR | 张勇洪/周超/刘小云团队合作揭示2-羟基异丁酰化修饰调控光暗适应性反应机制
景杰生物 | 报道 组蛋白赖氨酸的翻译后修饰是表观遗传学密码的重要组成部分,它们动态地调节染色质的结构和功能,影响基因表达活性,参与生物体的环境适应性调控.赖氨酸酰化修饰家族(Acylation) ...