Continuation
- Continuation表示当Task结束操作时,继续执行其他的一些操作,它通常是通过回调的方式来实现的。当Task一结束,就会开始执行。
1 | class Program |
- 在task上调用
GetAwaiter
会返回一个awaiter对象,它的OnCompleted
方法中的委托会在task结束或发生故障时执行。可以将Continuation附加到已经结束的task上面。 - 其实使用
task.Result
也可以获取task的返回值,但使用GetResult
的好处是,如果task发生故障,那么异常会被直接抛出,而不是包装在AggregateException
里面,这样catch就不用判断异常的类型。
ContinueWith
- 使用它的效果和使用
awaiter.OnCompleted
的效果一样。不同的是OnCompleted
没有返回值,而ContinueWith
返回原来Task实例,可以用它来附加更多的Continuation,但这样就必须直接处理AggregateException
。它要求传入一个原来Task实例类型的参数的委托。
1 | class Program |
TaskCompletionSource
- TaskCompletionSource是创建Task的另外一种方式,它可以让你在稍后开始和结束的任意操作中创建Task,提供了一个可手动执行的“从属”Task,即可指示操作何时结束或发生故障
- 一般推荐用它来创建IO-Bound类的工作,一是可以获得所有Task的优点,二是不需要在操作时阻塞线程。
- 下面是TaskCompletionSource的简易源码和它的使用方法。
1 | class Program |
TaskCompletionSource的真正用处是用它创建Task并不占用线程。
使用TaskCompletionSource自定义一个延迟函数,使用dotnet core新添加的Task.Delay可以达到相同的效果,但两者的区别是:一个是不会占用线程,它会等待Continuation开始后,才占用线程;而使用Task.Delay相当于异步版本的Thread.Sleep,它会并发地去执行任务。
1 | class Program |
同步和异步。。。
并发 并行 同步 异步 多线程
- 它们之间的区别:
- 并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。其中两种并发关系分别是同步和互斥
- 互斥:进程间相互排斥的使用临界资源的现象,就叫互斥。
- 同步:进程之间的关系不是相互排斥临界资源的关系,而是相互依赖的关系。进一步的说明:就是前一个进程的输出作为后一个进程的输入,当第一个进程没有输出时第二个进程必须等待。具有同步关系的一组并发进程相互发送的信息称为消息或事件。其中并发又有伪并发和真并发,伪并发是指单核处理器的并发,真并发是指多核处理器的并发。
- 并行:在单处理器中多道程序设计系统中,进程被交替执行,表现出一种并发的外部特种;在多处理器系统中,进程不仅可以交替执行,而且可以重叠执行。在多处理器上的程序才可实现并行处理。从而可知,并行是针对多处理器而言的。并行是同时发生的多个并发事件,具有并发的含义,但并发不一定并行,也亦是说并发事件之间不一定要同一时刻发生。
- 多线程:多线程是程序设计的逻辑层概念,它是进程中并发运行的一段代码。多线程可以实现线程间的切换执行。
- 异步:异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。
- 异步和多线程并不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段。异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情。实现异步可以采用多线程技术或则交给另外的进程来处理。
同步和异步
同步操作会在返回调用者之前完成它的工作,即在没有得到响应之前会一直等待,直到完成工作,把响应返回给调用者。
异步操作会在返回调用者之后完成它的(大部分)工作,即调用者调用之后,这个调用就直接返回了,没有返回结果(这时这个调用由task的Continuation负责),后面可以继续调用其他方法,而调用的结果会通过状态或通知来通知调用者。
使用异步会启用并发,因为它的工作会与调用者并发执行,异步方法通常会很快就返回到调用者,所以也叫非阻塞方法。
目前有以下几种异步方法:
- Thread.Start
- Task.Run
- Continuation
- …
异步编程
- 异步编程:就是将长时间运行的函数写成异步的;传统的方法是将长时间运行的函数写成同步的,然后按需引入并发
- 异步编程相对于传统同步编程有两个好处,一是大大减少了代码量;二是IO-bound并发可不适用线程来实现,可提高可扩展性和执行效率。
- 异步编程的用途是可编写高效处理大量并发IO的应用程序(服务器应用),它并不负责线程安全,而是关注执行效率,比如:每一个网络请求并不会消耗一个线程。
- 以下两种操作建议使用异步编写:
- IO-bound和Computed-bound操作
- 执行时间超过50毫秒的操作
- 另一方面过细粒度的并发可能会损害性能,因为异步操作也是开销。
异步函数
- async和await关键字可以让我们写出简洁的异步代码;
await
- await关键字简化了附加Continuation的过程,它的效果如下:
1 | //简写形式 |
可以await什么?一般await的表达式返回的是一个task,但满足下列条件的任意对象都可以被await:
- 拥有GetAwaiter方法,他返回一个awaiter(实现了INotifyCompletion.OnCompleted接口)。
- 拥有GetResult方法。
- 一个bool类型的IsCompleted属性。
注意:
await
都是和async
一起使用的,await是在函数的调用前面加上的,表示它是一个Continuation,若在方法体中使用了await
,就需要在方法返回值的前面加上async
。- 被await标记的语句的返回值的类型必须是Task,可以使用
Task<TResult>
来指定Continuation的结果。
async
- 使用了
async
修饰符的方法就是异步函数
,async
修饰符会让编译器把await
当作关键字而不是标识符,它只能应用于方法的返回值前(包括lambda表达式),用async修饰的方法可以返回void
、Task
、Task<TResult>
。 async
修饰符对方法的签名或访问修饰符(public…)没有影响,它只会影响方法内部。所以在接口中使用async
是没有意义的- 使用
async
来重载非async
方法是合法的
1 | class Program |
异步函数如何执行
- 当遇到
await
表达式时,执行会迅速地返回给调用者,就像yield return
一样,在返回前运行时会附加一个Continuation
到await地task上,为保证task结束时,执行会跳回原方法,从停止地地方继续执行。 - 如果发生故障,则异常会被重新抛出,如果正常运行,则task的返回值就会赋给await表达式。
- Lambda表达式中也可以写成异步的形式,写法和异步函数类似。
学习资料:B站杨旭