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 {
println("main runBlocking : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) {
println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) {
println("Default : I'm working in thread ${Thread.currentThread().name}")
}
launch(newSingleThreadContext("MyOwnThread")) {
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> {
val request = launch {
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")
}
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()
delay(1000)
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 {
delay((i + 1) * 200L)
println("Coroutine $i is done")
}
}
println("request: I'm done and I don't explicitly join my children that are still active")
}
request.join()
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")
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)
fun destroy() {
mainScope.cancel()
}
fun doSomething() {
repeat(10) { i ->
mainScope.launch {
delay((i + 1) * 200L)
println("Coroutine $i is done")
}
}
}
}
fun main() = runBlocking<Unit> {
val activity = Activity()
activity.doSomething()
println("Launched coroutines")
delay(500L)
println("Destroying activity!")
activity.destroy()
delay(1000)
}
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 {}
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?>()
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()}'")
}
|