Coroutine context and dispatchers

协程是在上下文环境中执行,CoroutineContext类型都在kotlin的标准库中定义

协程上下文是由一系列元素组成,最重要的是Job对象及调度器dispatcher

Dispatchers and threads 调度器和线程
dispatcher调度器可以调整协程运行时使用一个或者多个线程,也可以指定协程运行在特定的线程中,或者让协程不受限制的运行

协程构建器 launch{}函数 和async{}函数,都可以传递接收上下文参数,默认值:EmptyCoroutineContext

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {}

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {}
import kotlinx.coroutines.*

fun main() = runBlocking<Unit> {
    launch { // context of the parent, main runBlocking coroutine
        println("main runBlocking      : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
        println("Unconfined            : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher 
        println("Default               : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
        println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
    }    
}

Unconfined : I’m working in thread main @coroutine#3
Default : I’m working in thread DefaultDispatcher-worker-1 @coroutine#4
main runBlocking : I’m working in thread main @coroutine#2
newSingleThreadContext: I’m working in thread MyOwnThread @coroutine#5

调度器Dispatchers说明
launch { },无参 - 谁启动它,就使用coroutineScope{}它的上下文context
Dispatchers.Unconfined: 是一种特殊的调度器
Dispatchers.Default:使用JVM上的默认线程池,最大并发应该依赖cpu核心,最少2核也就是2核心,2线程
newSingleThreadContext(“MyOwnThread”):通过线程池创建一个新的线程
Dispatchers.IO IO线程
Dispatchers.Main 主线程

@DelicateCoroutinesApi
public actual fun newSingleThreadContext(name: String): ExecutorCoroutineDispatcher =
    newFixedThreadPoolContext(1, name)

@DelicateCoroutinesApi
public actual fun newFixedThreadPoolContext(nThreads: Int, name: String): ExecutorCoroutineDispatcher {
    require(nThreads >= 1) { "Expected at least one thread, but $nThreads specified" }
    val threadNo = AtomicInteger()
    val executor = Executors.newScheduledThreadPool(nThreads) { runnable ->
        val t = Thread(runnable, if (nThreads == 1) name else name + "-" + threadNo.incrementAndGet())
        t.isDaemon = true
        t
    }
    return executor.asCoroutineDispatcher()
}

Job in the context Job在协程上下文变量中
通过coroutineContext[Job] 获取Job

fun main() = runBlocking<Unit> {
    println("My job is ${coroutineContext[Job]}")    
}

Children of a coroutine 协程的子项
当协程A在另外一个协程B作用域启动时,协程A中的Job从协程B继承,就变成了协程B的子Job,当协程B取消时,其子项协程A也会被递归被取消

2种情况除外:
1,GlobalScope.launch
2,子协程有context参数,也就是传参是用了不同的Job,那么其Job不会从父类继承

fun main() = runBlocking<Unit> {
    // launch a coroutine to process some kind of incoming request
    val request = launch {
        // it spawns two other jobs
        launch(Job()) { 
            println("job1: I run in my own Job and execute independently!")
            delay(1000)
            println("job1: I am not affected by cancellation of the request")
        }
        // and the other inherits the parent context
        launch {
            delay(100)
            println("job2: I am a child of the request coroutine")
            delay(1000)
            println("job2: I will not execute this line if my parent request is cancelled")
        }
    }
    delay(500)
    request.cancel() // cancel processing of the request
    delay(1000) // delay a second to see what happens
    println("main: Who has survived request cancellation?")
}

job1: I run in my own Job and execute independently!
job2: I am a child of the request coroutine
job1: I am not affected by cancellation of the request
main: Who has survived request cancellation?

Parental responsibilities 父协程的责任
父协程总是等待所有子协程运行完成。不去追踪记录子协程的各种状态,在最后需要的时候,父协程统一处把协程加入进来,获取最终结果

val request = launch {
    repeat(3) { i -> // launch a few children jobs
        launch  {
            delay((i + 1) * 200L) // variable delay 200ms, 400ms, 600ms
            println("Coroutine $i is done")
        }
    }
    println("request: I'm done and I don't explicitly join my children that are still active")
}
request.join() // wait for completion of the request, including all its children
println("Now processing of the request is complete")

协程命名 给协程设置一个名字,方便调试用

需要配置-Dkotlinx.coroutines.debug,否则不会显示自己定义的协程名字

import kotlinx.coroutines.*

fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")

fun main() = runBlocking(CoroutineName("main")) {
    log("Started main coroutine")
    // run two background value computations
    val v1 = async(CoroutineName("v1coroutine")) {
        delay(500)
        log("Computing v1")
        252
    }
    val v2 = async(CoroutineName("v2coroutine")) {
        delay(1000)
        log("Computing v2")
        6
    }
    log("The answer for v1 / v2 = ${v1.await() / v2.await()}")    
}

[main @main#1] Started main coroutine
[main @v1coroutine#2] Computing v1
[main @v2coroutine#3] Computing v2
[main @main#1] The answer for v1 / v2 = 42

Combining context elements 混合定义上下文

比如同时定义 调度器和协程名称

fun main() = runBlocking<Unit> {
    launch(Dispatchers.Default + CoroutineName("test")) {
        println("I'm working in thread ${Thread.currentThread().name}")
    }    
}

I’m working in thread DefaultDispatcher-worker-1 @test#2

Coroutine scope 协程作用域

管理生命周期通过CoroutineScope类来管理,一般通过CoroutineScope() or MainScope()工厂函数来获取
CoroutineScope():通用
MainScope():主要用在UI应用程序,默认用Dispatchers.Main调度器

import kotlinx.coroutines.*

class Activity {
    private val mainScope = CoroutineScope(Dispatchers.Default) // use Default for test purposes
    
    fun destroy() {
        mainScope.cancel()
    }

    fun doSomething() {
        // launch ten coroutines for a demo, each working for a different time
        repeat(10) { i ->
            mainScope.launch {
                delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
                println("Coroutine $i is done")
            }
        }
    }
} // class Activity ends

fun main() = runBlocking<Unit> {
    val activity = Activity()
    activity.doSomething() // run test function
    println("Launched coroutines")
    delay(500L) // delay for half a second
    println("Destroying activity!")
    activity.destroy() // cancels all coroutines
    delay(1000) // visually confirm that they don't work    
}

Launched coroutines
Coroutine 0 is done
Coroutine 1 is done
Destroying activity!

只有前两个协程打印了信息,界面销毁的时候,通过协程取消,不再进行打印

在Android程序中,有生命周期的都支持协程

Thread-local data ThreadLocal数据和协程代码通信
对于ThreadLocal,可以通过asContextElement()扩展函数,它创建一个新的上下文Element,并在当前协程作用域中保持该值,出了协程作用域恢复为原来的值

源码:
public fun <T> ThreadLocal<T>.asContextElement(value: T = get()): ThreadContextElement<T> =
    ThreadLocalElement(value, this)

public interface ThreadContextElement<S> : CoroutineContext.Element {}

//重点看下updateThreadContext()和restoreThreadContext()方法
internal class ThreadLocalElement<T>(
    private val value: T,
    private val threadLocal: ThreadLocal<T>
) : ThreadContextElement<T> {
    override val key: CoroutineContext.Key<*> = ThreadLocalKey(threadLocal)

    override fun updateThreadContext(context: CoroutineContext): T {
        val oldState = threadLocal.get()
        threadLocal.set(value)
        return oldState
    }

    override fun restoreThreadContext(context: CoroutineContext, oldState: T) {
        threadLocal.set(oldState)
    }
import kotlinx.coroutines.*

val threadLocal = ThreadLocal<String?>() // declare thread-local variable

fun main() = runBlocking<Unit> {
    threadLocal.set("main")
    println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
    val job = launch(Dispatchers.Default + threadLocal.asContextElement(value = "launch")) {
        println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
        yield()
        println("After yield, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
    }
    job.join()
    println("Post-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")    
}

输出:
Pre-main, current thread: Thread[main,5,main], thread local value: ‘main’
Launch start, current thread: Thread[DefaultDispatcher-worker-1,5,main], thread local value: ‘launch’
After yield, current thread: Thread[DefaultDispatcher-worker-1,5,main], thread local value: ‘launch’
Post-main, current thread: Thread[main,5,main], thread local value: ‘main’

在协程作用域中改变ThreadLocal的值 通过withContext()函数

withContext(threadLocal.asContextElement(value = "协程中改变thread local的值")){
            println("withContext, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
        }