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. JobDeferred

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 值是 falsejob.isCancelled 值是 true

打开 job.join() 注释后,job.isCompleted 值是 truejob.isCancelled 值是 truejoin() 是等待所有孩子都完成,所以其后,它的 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

coroutines basics
kotlin 协程库


以上是关于Kotlin 协程 Basics的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 协程协程底层实现 ① ( Kotlin 协程分层架构 | 基础设施层 | 业务框架层 | 使用 Kotlin 协程基础设施层标准库 Api 实现协程 )

kotlin 协程万字协程 一篇完成kotlin 协程进阶

kotlin - Coroutine 协程

Kotlin 协程 基本认识

kotlin协程的生命周期与jetpack组件绑定

深潜Kotlin协程(十五):测试 Kotlin 协程