Kotlin 协程(2) Basics

文章目录

  • I. 开启协程的示例
    • i. `launch()` 是 `CoroutineScope` 的扩展函数
    • ii. `async()` 也是`CoroutineScope` 的扩展函数
    • iii. `runBlocking()` 就是一个函数
  • II. kotlinx 协程库中 `CoroutineScope` 族谱
    • i. 族谱
    • ii. 挂起函数 `coroutineScope()`
  • III. `Job`、`Deferred`
    • i. `Job` 简介
    • ii. `Job` 重要的函数: `cancel()`、`join()`、`cancelAndJoin`
  • IV. 总结
  • V. References


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 协程(2) Basics_第1张图片
上图,是基于 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语言,Kotlin)