(十七)Async异步和多线程-语言进阶1
- 一、进程-线程-多线程,同步和异步
- 1.概念
- 2.同步和异步
- 3.异步与多线程异同点
- 二、委托启动异步调用
- 1.同步方法
- 2.异步方法
- 三、多线程的特点
- 四、异步的回调和状态参数
- 1.顺序控制
- 2.状态参数
- 五、异步等待三种方式
- 1. Thread.sleep等待
- 2.asyncResult.AsyncWaitHandle.WaitOne()
- 3.action.EndInvoke()
- 六、异步返回值
一、进程-线程-多线程,同步和异步
1.概念
-
进程:进程是一个程序运行时,占用全部计算资源的总和,指在系统中正在运行的一个应用程序;程序一旦运行就是进程;或者更专业化来说:进程是指程序执行时的一个实例,即它是程序已经执行到课中程度的数据结构的汇集。从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。
-
线程:线程是程序执行流的最小单位,系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。任何操作都是由线程完成的;线程是依托于进程存在的,一个进程可以包含多个线程;线程也可以有自己的计算资源;
进程——资源分配的最小单位,线程——程序执行的最小单位。 -
多线程:多个执行流同时运行。
(1) CPU太快了,分时间片–上下文切换(加载环境–计算–保存环境)微观角度,一个核同一时刻只能执行一个线程;宏观的来说是多线程并发
(2) 名CPU多核可以独立工作,例如我们常说的 “4核8线程”–其中核是物理的核 8线程是指虚拟核,并不是上述我们所说的线程。 -
Thread:是c#语言对线程对象的封装
2.同步和异步
- 同步:完成计算之后在进入下一行
- 异步:不会等待方法的完成,会直接进入下一行,不会阻塞。
例如我们举一个贴切的例子,
同步:
“你和朋友去吃饭,但他有点忙,你就等他忙完一起吃,这就是同步方法”
异步:
“你和朋友去吃饭,但他有点忙,你就自己先去吃,他忙完自己再去吃,这就是异步方法”
3.异步与多线程异同点
- 异步和多线程都可以达到避免调用线程阻塞的目的
- 异步操作在完成await操作后,会发出完成通知,并释放占用的线程,之后系统调用线程池中空余的线程来进行await之后的操作,减少了线程负担。
- 而多线程编程会在整个任务中一直占用线程造成资源浪费。比如DMA(直接存储器访问)操作,允许硬件可以不通过CPU而直接与内存数据进行交互,在这时闲置的线程无法被释放,造成了资源浪费。(使用异步可以避免)
二、委托启动异步调用
下面我们可以通过一个示例看一下,分别执行5次
1.同步方法
同步方法
2.异步方法
异步方法
当我们调用同步方法时,程序是按照顺序执行,而下面的异步方法则是无序的,且执行速度也比较快。
三、多线程的特点
由上我们也可以看出同步和异步多线程方法的特点
- 同步方法卡界面,主(UI)线程忙于计算;
- 同步线程慢,因为只有一个线程在干活;
- 异步多线程方法不卡界面,主线程计算完,计算任务就交给子线程在做;例如winform可以提升用户体验;
- 异步多线程方法快,因为多个线程并发运算;
- 异步多线程无序:启动无序,执行时间不确定,结束也无序。
- 异步多线程并不是线性增长,而是资源换时间,但资源可能不够,其次多线程也有管理成本;
- 线程可以加快速度,但不是越多越好
下图可以清楚看到资源换时间的情况;
四、异步的回调和状态参数
1.顺序控制
从上面的示例中我们使用了异步多线程方式解决了很多问题,但也发现了一个特殊情况,异步是无序的,这时候我们如果想要控制顺序要怎么做呢。这个时候我们就可以使用回调的方式。
首先我们想在计算完成后,在完成后续动作,当我们在BeginInvoke中设置后续两个参数为null,可以看到下图的执行结果,我们的计算明明还没有完成,确提示我们已经完成了,只是主线程完成了。肯定不符合我们的结果。
这时候我们把鼠标放到BeginInvoke上可以发现框架已经帮我们提供好了异步回调AsyncCallback
我们可以看到AysncCallback是一个参数为IAsyncResult类型的委托
接下来我们修改代码把AsyncCallback传进来
这时我们再看执行结果,就可以看到我们结果实在最后输出的。
进行到这里你会想知道这是为什么呢,这个步骤是怎么完成的,callback的参数ia是怎么回事?
当我们继续把鼠标方法BeginInvake上,我们会发现它其实是一个IAsyncResult类型
你会发现这不是和上面AsyncCallback的参数ia是一样的吗,接下来我们做个验证
结果返回True
这时我们可以理解为,.net框架帮我们做了一个小封装,在执行BeginInvoke的时候会去申请一个线程,线程会先完成委托的调用,执行完成后会产生一个IAsyncResult结果,最后把这个结果作为参数传递给AsyncCallback委托,这也就能解释为什么BeginInvoke结果类型和AsyncCallback的参数类型是一致的。
2.状态参数
接下来我们看BeginInvoke的第三个参数,这时我们随便传入一个“hao”字符串。那么在AsyncState时我们就可以得到,这是为了我们在回调的时候如果想传递某些信息时使用。
五、异步等待三种方式
1. Thread.sleep等待
这种情况我们可以使用asyncResult.IsCompleted来进行判断委托是否执行完成。在执行完成之后给用户指定提示信息,如下为文件上传操作的示例
但是这么写是由弊端的就是会有延迟,也会卡主界面。优点就是我们可以在等待的时候做一些其它操作,例如给用户一些友好提示等。
2.asyncResult.AsyncWaitHandle.WaitOne()
信号量,即时等待
Console.WriteLine("Do Something Else.....");
Console.WriteLine("Do Something Else.....");
Console.WriteLine("Do Something Else.....");
asyncResult.AsyncWaitHandle.WaitOne();//等待任务的完成
也可以给asyncResult.AsyncWaitHandle.WaitOne(1000);设置等待时间做超时用,也叫做限时等待。
3.action.EndInvoke()
action.EndInvoke(asyncResult);
EndInvoke可以不仅等待也可以获取返回值,接下来介绍怎么获取返回值。
六、异步返回值
上述我们使用EndInvoke可以不仅等待也可以获取返回值;如下我们可以看到结果正是我们设置的字符串
EndInvoke也可以写在回调函数里面,方便我们获取其结果值,但只能写一次,写在里面就不可以写在外面了。