**目录**
[TOC]
## 协程上下文与调度器
协程总是运行在一些以[CoroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-coroutine-context/) 类型为代表的上下文中,它们被定义在了 Kotlin 的标准库里。
协程上下文是各种不同元素的集合。其中主元素是协程中的 [Job](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html),我们在前面的文档中见过它以及它的调度器,而本文将对它进行介绍。
### 调度器与线程
协程上下文包含一个 _协程调度器_ (参见 [CoroutineDispatcher](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html))它确定了哪些线程或与线程相对应的协程执行。协程调度器可以将协程限制在一个特定的线程执行,或将它分派到一个线程池,亦或是让它不受限地运行。
所有的协程构建器诸如 [launch](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html) 和 [async](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) 接收一个可选的[CoroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-coroutine-context/) 参数,它可以被用来显式的为一个新协程或其它上下文元素指定一个调度器。
尝试下面的示例:
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
//sampleStart
launch { // 运行在父协程的上下文中,即 runBlocking 主协程
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}")
}
//sampleEnd
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt)获取完整代码。
它执行后得到了如下输出(也许顺序会有所不同):
```text
Unconfined : I'm working in thread main
Default : I'm working in thread DefaultDispatcher-worker-1
newSingleThreadContext: I'm working in thread MyOwnThread
main runBlocking : I'm working in thread main
```
当调用 `launch { …… }` 时不传参数,它从启动了它的 [CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html)中承袭了上下文(以及调度器)。在这个案例中,它从 `main` 线程中的 `runBlocking`主协程承袭了上下文。
[Dispatchers.Unconfined](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html) 是一个特殊的调度器且似乎也运行在 `main` 线程中,但实际上,它是一种不同的机制,这会在后文中讲到。
该默认调度器,当协程在 [GlobalScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html) 中启动的时候使用,它代表 [Dispatchers.Default](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html) 使用了共享的后台线程池,所以 `GlobalScope.launch { …… }` 也可以使用相同的调度器—— `launch(Dispatchers.Default) { …… }`。
[newSingleThreadContext](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-single-thread-context.html) 为协程的运行启动了一个线程。一个专用的线程是一种非常昂贵的资源。在真实的应用程序中两者都必须被释放,当不再需要的时候,使用 [close](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-executor-coroutine-dispatcher/close.html)函数,或存储在一个顶层变量中使它在整个应用程序中被重用。
### 非受限调度器 vs 受限调度器
[Dispatchers.Unconfined](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html) 协程调度器在调用它的线程启动了一个协程,但它仅仅只是运行到第一个挂起点。挂起后,它恢复线程中的协程,而这完全由被调用的挂起函数来决定。非受限的调度器非常适用于执行不消耗 CPU 时间的任务,以及不更新局限于特定线程的任何共享数据(如UI)的协程。
另一方面,该调度器默认继承了外部的 [CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html)。[runBlocking](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) 协程的默认调度器,特别是,当它被限制在了调用者线程时,继承自它将会有效地限制协程在该线程运行并且具有可预测的 FIFO 调度。
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
//sampleStart
launch(Dispatchers.Unconfined) { // 非受限的——将和主线程一起工作
println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
delay(500)
println("Unconfined : After delay in thread ${Thread.currentThread().name}")
}
launch { // 父协程的上下文,主 runBlocking 协程
println("main runBlocking: I'm working in thread ${Thread.currentThread().name}")
delay(1000)
println("main runBlocking: After delay in thread ${Thread.currentThread().name}")
}
//sampleEnd
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt)获取完整代码。
执行后的输出
```text
Unconfined : I'm working in thread main
main runBlocking: I'm working in thread main
Unconfined : After delay in thread kotlinx.coroutines.DefaultExecutor
main runBlocking: After delay in thread main
```
所以,该协程的上下文继承自 `runBlocking {...}` 协程并在`main` 线程中运行,当 [delay](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html) 函数调用的时候,非受限的那个协程在默认的执行者线程中恢复执行。
> 非受限的调度器是一种高级机制,可以在某些极端情况下提供帮助而不需要调度协程以便稍后执行或产生不希望的副作用,因为某些操作必须立即在协程中执行。非受限调度器不应该在通常的代码中使用。
### 调试协程与线程
协程可以在一个线程上挂起并在其它线程上恢复。
甚至一个单线程的调度器也是难以弄清楚协程在何时何地正在做什么事情。使用通常调试应用程序的方法是让线程在每一个日志文件的日志声明中打印线程的名字。这种特性在日志框架中是普遍受支持的。但是在使用协程时,单独的线程名称不会给出很多协程上下文信息,所以`kotlinx.coroutines` 包含了调试工具来让它更简单。
使用 `-Dkotlinx.coroutines.debug` JVM 参数运行下面的代码:
```kotlin
import kotlinx.coroutines.*
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
fun main() = runBlocking<Unit> {
//sampleStart
val a = async {
log("I'm computing a piece of the answer")
6
}
val b = async {
log("I'm computing another piece of the answer")
7
}
log("The answer is ${a.await() * b.await()}")
//sampleEnd
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt)获取完整代码。
这里有三个协程,包括 `runBlocking` 内的主协程 (#1) ,以及计算延期的值的另外两个协程 `a` (#2) 和 `b` (#3)。它们都在 `runBlocking` 上下文中执行并且被限制在了主线程内。这段代码的输出如下:
```text
[main @coroutine#2] I'm computing a piece of the answer
[main @coroutine#3] I'm computing another piece of the answer
[main @coroutine#1] The answer is 42
```
这个 `log` 函数在方括号种打印了线程的名字,并且你可以看到它是 `main`线程,并且附带了当前正在其上执行的协程的标识符。这个标识符在调试模式开启时,将连续分配给所有创建的协程。
> 当 JVM 以 `-ea` 参数配置运行时,调试模式也会开启。你可以在 [DEBUG_PROPERTY_NAME](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-d-e-b-u-g_-p-r-o-p-e-r-t-y_-n-a-m-e.html) 属性的文档中阅读有关调试工具的更多信息。
### 在不同线程间跳转
使用 `-Dkotlinx.coroutines.debug` JVM 参数运行下面的代码(参见[调试](http://www.kotlincn.net/docs/reference/coroutines/coroutine-context-and-dispatchers.html#%E8%B0%83%E8%AF%95%E5%8D%8F%E7%A8%8B%E4%B8%8E%E7%BA%BF%E7%A8%8B)):
```kotlin
import kotlinx.coroutines.*
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
fun main() {
//sampleStart
newSingleThreadContext("Ctx1").use { ctx1 ->
newSingleThreadContext("Ctx2").use { ctx2 ->
runBlocking(ctx1) {
log("Started in ctx1")
withContext(ctx2) {
log("Working in ctx2")
}
log("Back to ctx1")
}
}
}
//sampleEnd
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt)获取完整代码。
它演示了一些新技术。其中一个使用 [runBlocking](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) 来显式指定了一个上下文,并且另一个使用 [withContext](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html) 函数来改变协程的上下文,而仍然驻留在相同的协程中,正如可以在下面的输出中所见到的:
```text
[Ctx1 @coroutine#1] Started in ctx1
[Ctx2 @coroutine#1] Working in ctx2
[Ctx1 @coroutine#1] Back to ctx1
```
注意,在这个例子中,当我们不再需要某个在 [newSingleThreadContext](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-single-thread-context.html) 中创建的线程的时候,它使用了 Kotlin 标准库中的 `use` 函数来释放该线程。
### 上下文中的作业
协程的 [Job](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html) 是上下文的一部分,并且可以使用`coroutineContext [Job]` 表达式在上下文中检索它:
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
//sampleStart
println("My job is ${coroutineContext[Job]}")
//sampleEnd
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt)获取完整代码。
在[调试模式](http://www.kotlincn.net/docs/reference/coroutines/coroutine-context-and-dispatchers.html#debugging-coroutines-and-threads)下,它将输出如下这些信息:
```
My job is "coroutine#1":BlockingCoroutine{Active}@6d311334
```
请注意,[CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html) 中的 [isActive](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/is-active.html) 只是`coroutineContext[Job]?.isActive == true` 的一种方便的快捷方式。
### 子协程
当一个协程被其它协程在 [CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html) 中启动的时候,它将通过 [CoroutineScope.coroutineContext](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/coroutine-context.html) 来承袭上下文,并且这个新协程的 [Job](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html) 将会成为父协程作业的 _子_ 作业。当一个父协程被取消的时候,所有它的子协程也会被递归的取消。
然而,当使用 [GlobalScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html) 来启动一个协程时,则新协程的作业没有父作业。因此它与这个启动的作用域无关且独立运作。
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
//sampleStart
// 启动一个协程来处理某种传入请求(request)
val request = launch {
// 孵化了两个子作业, 其中一个通过 GlobalScope 启动
GlobalScope.launch {
println("job1: I run in GlobalScope 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() // 取消请求(request)的执行
delay(1000) // 延迟一秒钟来看看发生了什么
println("main: Who has survived request cancellation?")
//sampleEnd
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt)获取完整代码。
这段代码的输出如下:
```text
job1: I run in GlobalScope 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?
```
### 父协程的职责
一个父协程总是等待所有的子协程执行结束。父协程并不显式的跟踪所有子协程的启动,并且不必使用 [Job.join](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html) 在最后的时候等待它们:
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
//sampleStart
// 启动一个协程来处理某种传入请求(request)
val request = launch {
repeat(3) { i -> // 启动少量的子作业
launch {
delay((i + 1) * 200L) // 延迟 200 毫秒、400 毫秒、600 毫秒的时间
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")
//sampleEnd
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt)获取完整代码。
>
结果如下所示:
```text
request: I'm done and I don't explicitly join my children that are still active
Coroutine 0 is done
Coroutine 1 is done
Coroutine 2 is done
Now processing of the request is complete
```
### 命名协程以用于调试
当协程经常打印日志并且你只需要关联来自同一个协程的日志记录时,则自动分配的 id 是非常好的。然而,当一个协程与特定请求的处理相关联时或做一些特定的后台任务,最好将其明确命名以用于调试目的。[CoroutineName](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-name/index.html) 上下文元素与线程名具有相同的目的。当[调试模式](http://www.kotlincn.net/docs/reference/coroutines/coroutine-context-and-dispatchers.html#debugging-coroutines-and-threads)开启时,它被包含在正在执行此协程的线程名中。
下面的例子演示了这一概念:
```kotlin
import kotlinx.coroutines.*
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
fun main() = runBlocking(CoroutineName("main")) {
//sampleStart
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()}")
//sampleEnd
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt)获取完整代码。
程序执行使用了 `-Dkotlinx.coroutines.debug` JVM 参数,输出如下所示:
```text
[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
```
### 组合上下文中的元素
有时我们需要在协程上下文中定义多个元素。我们可以使用 `+` 操作符来实现。比如说,我们可以显式指定一个调度器来启动协程并且同时显式指定一个命名:
```kotlin
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
//sampleStart
launch(Dispatchers.Default + CoroutineName("test")) {
println("I'm working in thread ${Thread.currentThread().name}")
}
//sampleEnd
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt)获取完整代码。
这段代码使用了 `-Dkotlinx.coroutines.debug` JVM 参数,输出如下所示:
```text
I'm working in thread DefaultDispatcher-worker-1 @test#2
```
### 协程作用域
让我们将关于上下文,子协程以及作业的知识综合在一起。假设我们的应用程序拥有一个具有生命周期的对象,但这个对象并不是一个协程。举例来说,我们编写了一个 Android 应用程序并在 Android 的 activity 上下文中启动了一组协程来使用异步操作拉取并更新数据以及执行动画等等。所有这些协程必须在这个 activity 销毁的时候取消以避免内存泄漏。当然,我们也可以手动操作上下文与作业,以结合 activity 的生命周期与它的协程,但是`kotlinx.coroutines` 提供了一个封装:[CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html) 的抽象。你应该已经熟悉了协程作用域,因为所有的协程构建器都声明为在它之上的扩展。
我们通过创建一个 [CoroutineScope] 实例来管理协程的生命周期,并使它与activity 的生命周期相关联。`CoroutineScope` 可以通过 [CoroutineScope()] 创建或者通过[MainScope()](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html)工厂函数。前者创建了一个通用作用域,而后者为使用 [Dispatchers.Main] 作为默认调度器的 UI 应用程序创建作用域:
```kotlin
class Activity {
private val mainScope = MainScope()
fun destroy() {
mainScope.cancel()
}
// 继续运行……
```
或者,我们可以在这个 `Activity` 类中实现 [CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html) 接口。最好的方法是使用具有默认工厂函数的委托。
我们也可以将所需的调度器与作用域合并(我们在这个示例中使用 [Dispatchers.Default](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html))。
```kotlin
class Activity : CoroutineScope by CoroutineScope(Dispatchers.Default) {
// 继续运行……
```
现在,在这个 `Activity` 的作用域中启动协程,且没有明确指定它们的上下文。在示例中,我们启动了十个协程并延迟不同的时间:
```kotlin
// 在 Activity 类中
fun doSomething() {
// 在示例中启动了 10 个协程,且每个都工作了不同的时长
repeat(10) { i ->
launch {
delay((i + 1) * 200L) // 延迟 200 毫秒、400 毫秒、600 毫秒等等不同的时间
println("Coroutine $i is done")
}
}
}
} // Activity 类结束
```
在 main 函数中我们创建 activity,调用测试函数 `doSomething`,并且在 500 毫秒后销毁这个 activity。这取消了从 `doSomething` 启动的所有协程。我们可以观察到这些是由于在销毁之后,即使我们再等一会儿,activity 也不再打印消息。
```kotlin
import kotlin.coroutines.*
import kotlinx.coroutines.*
class Activity : CoroutineScope by CoroutineScope(Dispatchers.Default) {
fun destroy() {
cancel() // Extension on CoroutineScope
}
// 继续运行……
// class Activity continues
fun doSomething() {
// 在示例中启动了 10 个协程,且每个都工作了不同的时长
repeat(10) { i ->
launch {
delay((i + 1) * 200L) // 延迟 200 毫秒、400 毫秒、600 毫秒等等不同的时间
println("Coroutine $i is done")
}
}
}
} // Activity 类结束
fun main() = runBlocking<Unit> {
//sampleStart
val activity = Activity()
activity.doSomething() // 运行测试函数
println("Launched coroutines")
delay(500L) // 延迟半秒钟
println("Destroying activity!")
activity.destroy() // 取消所有的协程
delay(1000) // 为了在视觉上确认它们没有工作
//sampleEnd
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt)获取完整代码。
这个示例的输出如下所示:
```text
Launched coroutines
Coroutine 0 is done
Coroutine 1 is done
Destroying activity!
```
你可以看到,只有前两个协程打印了消息,而另一个协程在`Activity.destroy()` 中单次调用了 `job.cancel()`。
### 线程局部数据
有时,能够将一些线程局部数据传递到协程与协程之间是很方便的。 然而,由于它们不受任何特定线程的约束,如果手动完成,可能会导致出现样板代码。
[`ThreadLocal`](https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html),[asContextElement](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/java.lang.-thread-local/as-context-element.html) 扩展函数在这里会充当救兵。它创建了额外的上下文元素,且保留给定 `ThreadLocal` 的值,并在每次协程切换其上下文时恢复它。
它很容易在下面的代码中演示:
```kotlin
import kotlinx.coroutines.*
val threadLocal = ThreadLocal<String?>() // 声明线程局部变量
fun main() = runBlocking<Unit> {
//sampleStart
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()}'")
//sampleEnd
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt)获取完整代码。
在这个例子中我们使用 [Dispatchers.Default](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html) 在后台线程池中启动了一个新的协程,所以它工作在线程池中的不同线程中,但它仍然具有线程局部变量的值,我们指定使用 `threadLocal.asContextElement(value = "launch")`,无论协程执行在什么线程中都是没有问题的。因此,其输出如([调试](http://www.kotlincn.net/docs/reference/coroutines/coroutine-context-and-dispatchers.html#%E8%B0%83%E8%AF%95%E5%8D%8F%E7%A8%8B%E4%B8%8E%E7%BA%BF%E7%A8%8B))所示:
```text
Pre-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main'
Launch start, current thread: Thread[DefaultDispatcher-worker-1 @coroutine#2,5,main], thread local value: 'launch'
After yield, current thread: Thread[DefaultDispatcher-worker-2 @coroutine#2,5,main], thread local value: 'launch'
Post-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main'
```
这很容易忘记去设置相应的上下文元素。如果运行协程的线程不同,在协程中访问的线程局部变量则可能会产生意外的值。为了避免这种情况,建议使用 [ensurePresent](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/java.lang.-thread-local/ensure-present.html)方法并且在不正确的使用时快速失败。
`ThreadLocal` 具有一流的支持,可以与任何 `kotlinx.coroutines` 提供的原语一起使用。但它有一个关键限制,即:当一个线程局部变量变化时,则这个新值不会传播给协程调用者(因为上下文元素无法追踪所有 `ThreadLocal` 对象访问),并且下次挂起时更新的值将丢失。使用 [withContext](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html) 在协程中更新线程局部变量,详见 [asContextElement](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/java.lang.-thread-local/as-context-element.html)。
另外,一个值可以存储在一个可变的域中,例如 `class Counter(var i: Int)`,是的,反过来,可以存储在线程局部的变量中。然而,在这个案例中你完全有责任来进行同步可能的对这个可变的域进行的并发的修改。
对于高级的使用,例如,那些在内部使用线程局部传递数据的用于与日志记录 MDC 集成,以及事务上下文或任何其它库,请参见需要实现的[ThreadContextElement](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-thread-context-element/index.html) 接口的文档。
[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
[Dispatchers.Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html
[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
[newSingleThreadContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-single-thread-context.html
[ExecutorCoroutineDispatcher.close]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-executor-coroutine-dispatcher/close.html
[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
[DEBUG_PROPERTY_NAME]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-d-e-b-u-g_-p-r-o-p-e-r-t-y_-n-a-m-e.html
[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
[isActive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/is-active.html
[CoroutineScope.coroutineContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/coroutine-context.html
[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
[CoroutineName]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-name/index.html
[CoroutineScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope.html
[MainScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html
[Dispatchers.Main]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
[asContextElement]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/java.lang.-thread-local/as-context-element.html
[ensurePresent]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/java.lang.-thread-local/ensure-present.html
[ThreadContextElement]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-thread-context-element/index.html
- 前言
- Kotlin简介
- IntelliJ IDEA技巧总结
- idea设置类注释和方法注释模板
- 像Android Studion一样创建工程
- Gradle
- Gradle入门
- Gradle进阶
- 使用Gradle创建一个Kotlin工程
- 环境搭建
- Androidstudio平台搭建
- Eclipse的Kotlin环境配置
- 使用IntelliJ IDEA
- Kotlin学习路线
- Kotlin官方中文版文档教程
- 概述
- kotlin用于服务器端开发
- kotlin用于Android开发
- kotlin用于JavaScript开发
- kotlin用于原生开发
- Kotlin 用于数据科学
- 协程
- 多平台
- 新特性
- 1.1的新特性
- 1.2的新特性
- 1.3的新特性
- 开始
- 基本语法
- 习惯用法
- 编码规范
- 基础
- 基本类型
- 包与导入
- 控制流
- 返回与跳转
- 类与对象
- 类与继承
- 属性与字段
- 接口
- 可见性修饰符
- 扩展
- 数据类
- 密封类
- 泛型
- 嵌套类
- 枚举类
- 对象
- 类型别名
- 内嵌类
- 委托
- 委托属性
- 函数与Lambda表达式
- 函数
- Lambda表达式
- 内联函数
- 集合
- 集合概述
- 构造集合
- 迭代器
- 区间与数列
- 序列
- 操作概述
- 转换
- 过滤
- 加减操作符
- 分组
- 取集合的一部分
- 取单个元素
- 排序
- 聚合操作
- 集合写操作
- List相关操作
- Set相关操作
- Map相关操作
- 多平台程序设计
- 平台相关声明
- 以Gradle创建
- 更多语言结构
- 解构声明
- 类型检测与转换
- This表达式
- 相等性
- 操作符重载
- 空安全
- 异常
- 注解
- 反射
- 作用域函数
- 类型安全的构造器
- Opt-in Requirements
- 核心库
- 标准库
- kotlin.test
- 参考
- 关键字与操作符
- 语法
- 编码风格约定
- Java互操作
- Kotlin中调用Java
- Java中调用Kotlin
- JavaScript
- 动态类型
- kotlin中调用JavaScript
- JavaScript中调用kotlin
- JavaScript模块
- JavaScript反射
- JavaScript DCE
- 原生
- 并发
- 不可变性
- kotlin库
- 平台库
- 与C语言互操作
- 与Object-C及Swift互操作
- CocoaPods集成
- Gradle插件
- 调试
- FAQ
- 协程
- 协程指南
- 基础
- 取消与超时
- 组合挂起函数
- 协程上下文与调度器
- 异步流
- 通道
- 异常处理与监督
- 共享的可变状态与并发
- Select表达式(实验性)
- 工具
- 编写kotlin代码文档
- 使用Kapt
- 使用Gradle
- 使用Maven
- 使用Ant
- Kotlin与OSGI
- 编译器插件
- 编码规范
- 演进
- kotlin语言演进
- 不同组件的稳定性
- kotlin1.3的兼容性指南
- 常见问题
- FAQ
- 与Java比较
- 与Scala比较(官方已删除)
- Google开发者官网简介
- Kotlin and Android
- Get Started with Kotlin on Android
- Kotlin on Android FAQ
- Android KTX
- Resources to Learn Kotlin
- Kotlin样品
- Kotlin零基础到进阶
- 第一阶段兴趣入门
- kotlin简介和学习方法
- 数据类型和类型系统
- 入门
- 分类
- val和var
- 二进制基础
- 基础
- 基本语法
- 包
- 示例
- 编码规范
- 代码注释
- 异常
- 根类型“Any”
- Any? 可空类型
- 可空性的实现原理
- kotlin.Unit类型
- kotlin.Nothing类型
- 基本数据类型
- 数值类型
- 布尔类型
- 字符型
- 位运算符
- 变量和常量
- 语法和运算符
- 关键字
- 硬关键字
- 软关键字
- 修饰符关键字
- 特殊标识符
- 操作符和特殊符号
- 算术运算符
- 赋值运算符
- 比较运算符
- 逻辑运算符
- this关键字
- super关键字
- 操作符重载
- 一元操作符
- 二元操作符
- 字符串
- 字符串介绍和属性
- 字符串常见方法操作
- 字符串模板
- 数组
- 数组介绍创建及遍历
- 数组常见方法和属性
- 数组变化以及下标越界问题
- 原生数组类型
- 区间
- 正向区间
- 逆向区间
- 步长
- 类型检测与类型转换
- is、!is、as、as-运算符
- 空安全
- 可空类型变量
- 安全调用符
- 非空断言
- Elvis操作符
- 可空性深入
- 可空性和Java
- 函数
- 函数式编程概述
- OOP和FOP
- 函数式编程基本特性
- 组合与范畴
- 在Kotlin中使用函数式编程
- 函数入门
- 函数作用域
- 函数加强
- 命名参数
- 默认参数
- 可变参数
- 表达式函数体
- 顶层、嵌套、中缀函数
- 尾递归函数优化
- 函数重载
- 控制流
- if表达式
- when表达式
- for循环
- while循环
- 循环中的 Break 与 continue
- return返回
- 标签处返回
- 集合
- list集合
- list集合介绍和操作
- list常见方法和属性
- list集合变化和下标越界
- set集合
- set集合介绍和常见操作
- set集合常见方法和属性
- set集合变换和下标越界
- map集合
- map集合介绍和常见操作
- map集合常见方法和属性
- map集合变换
- 集合的函数式API
- map函数
- filter函数
- “ all ”“ any ”“ count ”和“ find ”:对集合应用判断式
- 别样的求和方式:sumBy、sum、fold、reduce
- 根据人的性别进行分组:groupBy
- 扁平化——处理嵌套集合:flatMap、flatten
- 惰性集合操作:序列
- 区间、数组、集合之间转换
- 面向对象
- 面向对象-封装
- 类的创建及属性方法访问
- 类属性和字段
- 构造器
- 嵌套类(内部类)
- 枚举类
- 枚举类遍历&枚举常量常用属性
- 数据类
- 密封类
- 印章类(密封类)
- 面向对象-继承
- 类的继承
- 面向对象-多态
- 抽象类
- 接口
- 接口和抽象类的区别
- 面向对象-深入
- 扩展
- 扩展:为别的类添加方法、属性
- Android中的扩展应用
- 优化Snackbar
- 用扩展函数封装Utils
- 解决烦人的findViewById
- 扩展不是万能的
- 调度方式对扩展函数的影响
- 被滥用的扩展函数
- 委托
- 委托类
- 委托属性
- Kotlin5大内置委托
- Kotlin-Object关键字
- 单例模式
- 匿名类对象
- 伴生对象
- 作用域函数
- let函数
- run函数
- with函数
- apply函数
- also函数
- 标准库函数
- takeIf 与 takeUnless
- 第二阶段重点深入
- Lambda编程
- Lambda成员引用高阶函数
- 高阶函数
- 内联函数
- 泛型
- 泛型的分类
- 泛型约束
- 子类和子类型
- 协变与逆变
- 泛型擦除与实化类型
- 泛型类型参数
- 泛型的背后:类型擦除
- Java为什么无法声明一个泛型数组
- 向后兼容的罪
- 类型擦除的矛盾
- 使用内联函数获取泛型
- 打破泛型不变
- 一个支持协变的List
- 一个支持逆变的Comparator
- 协变和逆变
- 第三阶段难点突破
- 注解和反射
- 声明并应用注解
- DSL
- 协程
- 协程简介
- 协程的基本操作
- 协程取消
- 管道
- 慕课霍丙乾协程笔记
- Kotlin与Java互操作
- 在Kotlin中调用Java
- 在Java中调用Kotlin
- Kotlin与Java中的操作对比
- 第四阶段专题练习
- 朱凯Kotlin知识点总结
- Kotlin 基础
- Kotlin 的变量、函数和类型
- Kotlin 里那些「不是那么写的」
- Kotlin 里那些「更方便的」
- Kotlin 进阶
- Kotlin 的泛型
- Kotlin 的高阶函数、匿名函数和 Lambda 表达式
- Kotlin协程
- 初识
- 进阶
- 深入
- Kotlin 扩展
- 会写「18.dp」只是个入门——Kotlin 的扩展函数和扩展属性(Extension Functions / Properties)
- Kotlin实战-开发Android