Kotlin 协程 Basics
Posted 匆忙拥挤repeat
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin 协程 Basics相关的知识,希望对你有一定的参考价值。
文章目录
I. 开启协程的示例
//全局式. 协程的生命周期只受整个应用程序的生命周期的限制。
GlobalScope.launch(Dispatchers.Unconfined)
println("hello, ($Thread.currentThread().name)")
delay(1000L)
println("stone")
// GlobalScope.async
//阻塞式
runBlocking
delay(1000L) //与上一个delay基本是同时执行的,只晚一点点。
println("Did you win the lottery?")
//阻塞式 返回 T
val v = runBlocking
delay(1500L)
GlobalScope.launch
println("yeah. $2")
delay(500)
"i win"
println(v)
i. launch()
是 CoroutineScope
的扩展函数
看源码,
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
内部会构建一个 coroutine
实例,并调用 start()
,来启动协程。返回 Job
实例。
ii. async()
也是CoroutineScope
的扩展函数
内部会构建一个 coroutine
实例,并调用 start()
,来启动协程。返回 Deferred
实例。
看函数名是异步的意思,但并不是说它就一定在异步线程中运行。而是其函数可能不是立即执行的。
iii. runBlocking()
就是一个函数
@Throws(InterruptedException::class)
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T
val currentThread = Thread.currentThread()
val contextInterceptor = context[ContinuationInterceptor]
val eventLoop: EventLoop?
val newContext: CoroutineContext
if (contextInterceptor == null)
// create or use private event loop if no dispatcher is specified
eventLoop = ThreadLocalEventLoop.eventLoop
newContext = GlobalScope.newCoroutineContext(context + eventLoop)
else
// See if context's interceptor is an event loop that we shall use (to support TestContext)
// or take an existing thread-local event loop if present to avoid blocking it (but don't create one)
eventLoop = (contextInterceptor as? EventLoop)?.takeIf it.shouldBeProcessedFromContext()
?: ThreadLocalEventLoop.currentOrNull()
newContext = GlobalScope.newCoroutineContext(context)
val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
return coroutine.joinBlocking()
内部会构建一个 coroutine
实例,并调用 start()
,来启动协程。返回值是 传入的 block()
里的最后一行。所以,示例代码中,会输出 i win
。
这是阻塞式的实现,即 可以理解成是同步的,非异步的实现。阻塞的是调用者线程。
控制台程序中,若在某个非阻塞协程中使用 delay(),可能主函数 main()已经执行完了,而看不到后续的操作。所以,可以将main(),声明成:
fun main() = runBlocking ...
II. kotlinx 协程库中 CoroutineScope
族谱
i. 族谱
上图,是基于 kotlin.coroutines 1.3.5 的协程库(非安卓上的)中的 CoroutineScope
的族谱。
public interface CoroutineScope
public val coroutineContext: CoroutineContext
看源码中 CoroutineScope
的定义,内部定义了一个常量 coroutineContext
,实际上它是抽象的。如自定义一个 CoroutineScope
:
class MyContextScope(override val coroutineContext: CoroutineContext) : CoroutineScope
结合上图,可知,sdk内部实现的这些
XxxCoroutine
协程,其实都实现了CoroutineScope
。
它们会对 coroutineContext 进行赋值处理。
ii. 挂起函数 coroutineScope()
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R =
suspendCoroutineUninterceptedOrReturn uCont ->
val coroutine = ScopeCoroutine(uCont.context, uCont)
coroutine.startUndispatchedOrReturn(coroutine, block)
该sdk函数,是一个挂起函数,内部会创建一个 coroutine
协程实例。
因其是挂起函数,所以必须要被一个协程或另一个挂起函数调用。
fun main(args: Array<String>) = runBlocking
coroutineScope
launch
delay(500L)
println("Task from nested launch")
delay(100L)
println("Task from coroutine scope") // This line will be printed before the nested launch
III. Job
、Deferred
i. Job
简介
launch()
返回 Job
,其实现了 CoroutineContext
接口;内部有三个属性:
public val isActive: Boolean
public val isCompleted: Boolean
public val isCancelled: Boolean
文档注释中,有个如下简单的流程示意图,来描述了上面三个属性的作用:
wait children
+-----+ start +--------+ complete +-------------+ finish +-----------+
| New | -----> | Active | ---------> | Completing | -------> | Completed |
+-----+ +--------+ +-------------+ +-----------+
| cancel / fail |
| +----------------+
| |
V V
+------------+ finish +-----------+
| Cancelling | --------------------------------> | Cancelled |
+------------+ +-----------+
新的 Job 实例,启动后, 进入 Active 状态;
Active 状态的,可以进行取消操作,取消完成后,进入 Cancelled 状态;
Active 状态的,正常执行完成的(如果有子Job,等待所有子Job都执行完成),进入 Completed 状态;如果自身或子Job未执行完成时,被取消或发生了错误,进入 Cancelled 状态。
ii. Job
重要的函数: cancel()
、join()
、cancelAndJoin
来个示例
val job = GlobalScope.launch
delay(1000L)
launch
delay(8000)
println(8/0)
println("World!")
job.cancel()
println("Hello,")
// job.join() // wait until child coroutine completes
println("unprecedented $job.isCompleted")
println("unprecedented $job.isCancelled")
cancel()
取消Job,其实就是取消了GlobalScope.launch
整体的协程任务。( delay(1000)之前有其它代码还是会执行的。 )job.isCompleted 值是 false
, job.isCancelled 值是 true
打开 job.join()
注释后,job.isCompleted 值是 true
, job.isCancelled 值是 true
。join()
是等待所有孩子都完成,所以其后,它的 isCompleted 是 true
。
cancelAndJoin()
内部实现就是先调用 cancel()
再调用 join()
对于 cancel() 并不是一调用就会立即取消的。它在遇到 会使协程 『挂起』的操作时,才能取消成功。
iii. Job
的子类Deferred
async()
返回 Deferred。它多了一个重要的方法 await()
。
// 挂起函数中,调用其它挂起函数;挂起协程
suspend fun doWorld()
delay(1000L)
println("World!")
repeat(10) i ->
print("$i\\t")
println()
//协程中 调用挂起函数
val deferred1 = async
doWorld()
"done $Thread.currentThread().name"
println(deferred1.await())
val deferred2 = async(start = CoroutineStart.LAZY)
println("时间:$System.currentTimeMillis()")
"done $Thread.currentThread().name"
delay(3000)
println(deferred2.await())
async()
默认会立即执行,如 val r = async ...
;调用 deferred1.await()
能获取到结果。
val deferred2 = async(start = CoroutineStart.LAZY) ...
当 start 是 Lazy时,协程会在 调用 deferred2.await()
时才开始执行。
IV. 总结
要开启怎样的一个协程:
GlobalScope 全局的,内部有线程池,可选 launch() 、 async()
非全局的,且不指定与异步线程相关的 CoroutineContext 参数时,默认就是执行在调用者线程中。
launch() 返回 Job, async() 返回 Deferred。根据是否要操作返回值,进行选择。
runBlocking() 会阻塞调用者线程,注意它需要一个返回值,即使是 Unit 的空类型。
挂起函数 coroutineScope() 能创建一个非全局的协程。
V. References
以上是关于Kotlin 协程 Basics的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin 协程协程底层实现 ① ( Kotlin 协程分层架构 | 基础设施层 | 业务框架层 | 使用 Kotlin 协程基础设施层标准库 Api 实现协程 )