赞
踩
它是一种用户态的轻量级线程,协程的调度完全由用户控制。它有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁地访问全局变量,所以上下文的切换非常快。协程虽然是微线程,但并不会和某一个线程绑定,它可以在A线程中执行,经过某一个时刻的挂起,等下次调度到恢复执行的时候,很可能会在B线程中执行。
线程的阻塞代价是昂贵的,而协程使用了更简单、代价更小的挂起来代替阻塞
1.CoroutineContext
协程上下文,它包含一个默认的协程调度器,所有协程都必须在CoroutineContext
2.CoroutineScope
协程作用域,它一个接口只包含一个属性coroutineContext。它定义了一个协程的作用范围。每个Coroutine builder都是CoroutineScope的扩展,CoroutinesScope会在具有生命周期的实体上实现。Kotlin定义一个全局的作用域GlobalScope
,用于启动顶级的协程,这些协程会在整个应用程序生命周期内运行
3.CoroutineDispatcher
协程调度器,它用来调度和处理任务,决定了相关协程应该在哪个或哪些线程中执行,kotlin的协程包含多种协程调度器
4.Suspend
关键字,协程可以被挂起而无需阻塞线程。我们使用suspend关键字来修饰可以被挂起的函数。被标记为suspend的函数只能运行在协程或者其他suspend函数中。suspend可以修饰普通函数、扩展函数和Lambda表达式
5.suspension point
协程每个挂起的地方是一个suspension point
6.Continuation
字面意思是继续、持续的意思。由于协程可能是分段执行的:先执行一段,挂起,再执行一段,再挂起…相邻的两个suspension point之间被称为Continuation。Continuation用来表示每一段执行的代码,一个完整的协程程序包含多个Continuation
7.Job
任务执行的过程被封装成Job,交给协程调度器处理。Job是一种具有简单生命周期的可取消任务,当父类的Job被取消时,子类的Job也会被取消,Job拥有三种状态:isActive、isCompleted、isCancelled
8.Deferred
是Job的子类,Job完成时是没有返回值的,而Derferred在任务完成时能够提供返回值
每一个Coroutine builders是一个函数,它接受一个suspend的Lambda表达式并创建一个协程来运行它
在kotlin中创建携程很简单,它会启动一个新的协程,返回一个Job
对象
async跟launch的用法基本一致,使用async会返回一个Deferred
对象。因此async创建的协程拥有返回值,通过await()
方法将值返回(若要在线程中返回值,直接使用Thread会比较麻烦,可考虑使用Future)
launch、async的start参数用于指定协程应该何时开始
查看async源码
在默认情况下,当执行到具有launch、async函数时,会立即启动协程内部的工作,因为start参数的默认值是CoroutineStart.DEFAULT
。当start参数使用CoroutineStart.LAZY
值时,只有在返回的Job或Deferred对象显式调用start()
、join()
或await()
(只有Deferred才有await())时才会启动协程,类似于懒加载
如果上述代码最外层launch函数的start参数也用了CoroutineStart.LAZY,则result的结果不会打印,因为最外层launch返回的job对象并没有被调用
launch、async的invokeOnCompletion函数会对它们的执行结果进行回调,支持异常的处理
其中cancelAndJoin()
表示取消任务。由于res已经被取消了,因此res.await()获取不到结果。最后,Job、Deferred对象都可以取消任务(使用cancel()、cancelAndjoin())
runBlocking创建的协程直接运行在当前线程上,同时阻塞当前线程直到结束。在runBlocking内可以创建其他协程,例如launch。但是反过来,在launch中使用runBlocking则不行。调用了runBlocking的主线程会一直阻塞直到reunBlocking内部的协程执行完毕
runBlocking在使用时可以指定CoroutineDispatcher
,此时创建的协程会在指定的协程调度器中运行,同时阻塞当前线程。runBlocking最后一行的值即为它的返回值
协程提供了避免阻塞线程,并用更廉价、更可控的操作替代线程阻塞的方法:协程挂起。
挂起函数比普通函数多了一个关键字suspend
修饰,kotlin的挂起函数采用CPS(Continuation Passing Style)和Switch状态机。能够保证每次挂起之后,后面的代码会在挂起函数执行完再继续执行。CPS是一种函数不直接返回值的代码风格。在这种风格中,函数将结果传入一个延续(Continuation,指"之后的内容"),后者决定了之后的逻辑
delay
delay()是常见的挂起函数,类似于线程的sleep()函数(运行在该线程下的所有协程都会被阻塞),但delay()不会阻塞线程
上面的代码中,在GlobalScope中启动了一个新的协程,意味着新协程的生命周期只受整个应用程序的生命周期限制。协程创建之后就会立即打印3,之后挂起,一秒后恢复之前的协程并打印4.而sleep一开始阻塞了主线程2秒,等主线程唤醒后立刻打印5.
yield
yield()用于挂起当前的协程,将当前的协程分发到CoroutineDispatcher
的队列,等其他协程完成/挂起之后,再继续执行先前的协程
withContext
withContext不会创建新的协程。withContext类似runBlocking,它的最后一行的值即为withContext的返回值,而且会阻塞上下文线程,通过Dispathcers来指定代码块运行的线程
withContext不像launch、async的context参数都是默认参数,并且使用默认值EmptyCoroutineContext。
withContext的context参数必须传值,它是CoroutineContext
类型的。在使用withContext时,可以使用不同的CoroutineDispatcher
,例如Dispatchers.Default(取代原先的CommonPool,kotlin1.3之后CommonPool变成了internal object)。
因为CoroutineDispatcher实现了CoroutineContext接口,所以这里传递CoroutineDispatcher
Dispatchers提供了CoroutineDispatcher的多种实现,有点类似于RxJava的Schedulers
Default
表示使用后台线程的公共线程池
IO
适用于I/O密集型操作的线程池
Unconfined
表示在被调用的线程中启动协程,直到程序运行到第一个挂起点,协程会在相应的挂起函数所使用的任何线程中恢复
此外,常见的CoroutineDispatcher还可以通过ThreadPoolDispatcher的newSingleThreadContext()
、newFixedThreadPoolContext()
来创建,以及Executor的扩展函数asCoroutineDispatcher()
来创建。在Android中经常会用到Diapatchers.Main
,它同样继承自CoroutinueDispatcher。使用之后可以在Android主线程上调度执行
withContext在使用NonCancellable
时,能够让协程的任务执行完,即使会被调用者取消
coroutineScope
跟withContext类似,coroutineScope也会有返回值,但是coroutineScope采用父协程的CoroutineContext,无法使用其他的CoroutineDispatcher
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。