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()}'")
}