├── README.md └── guide ├── example ├── basic.md ├── cancelandtimeout.md ├── channel.md ├── componsecoroutine.md ├── contextdispatchers.md └── main.md └── imgs └── coroutine.jpg /README.md: -------------------------------------------------------------------------------- 1 | # kotlinx.coroutines-zh 2 | 3 | kotlinx.coroutines官方文档中文版本 4 | 5 | 本文翻译的是 Kotlinx.coroutines 的官方GitHub文档。 6 | 原文地址: 7 | 8 | [Guide to kotlinx.coroutines by example](https://github.com/shaomaicheng/kotlinx.coroutines/blob/master/coroutines-guide.md) 9 | 10 | 11 | ## 目录 12 | 13 | #### [用实例入门Kotlinx.couroutines](./guide/example/main.md) 14 | 15 | #### [协程基础](./guide/example/basic.md) 16 | 17 | #### [取消和超时](./guide/example/cancelandtimeout.md) 18 | 19 | #### [组合 suspending 函数](./guide/example/componsecoroutine.md) 20 | 21 | #### [协程上下文和调度器](./guide/example/contextdispatchers.md) 22 | 23 | #### [Channels](./guide/example/channel.md) 24 | 25 | 26 | ## 个人小结 27 | 28 | 阅读了几次 Kotlin -- Guide to kotlinx.coroutines by example的协程官方文档。这次顺手把前面5节翻译了下来。在过程中对某些语句进行了推敲,感觉自己的英文文档阅读能力有了不少提升,同时也希望能帮助到其他人。但是自己的英文水平也毕竟有限,所以也希望能有其他同学一起参与后面章节的翻译。 29 | 30 | 针对这个文档的内容,我添加了一份xmind供大家参考。如果有错误,欢迎提出来纠正和讨论。 31 | 32 | 33 | 34 | ![](https://github.com/shaomaicheng/kotlinx.coroutines-zh/blob/master/guide/imgs/coroutine.jpg?raw=true) 35 | -------------------------------------------------------------------------------- /guide/example/basic.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #### 协程基础 5 | 6 | 本节涵盖协程的基本概念 7 | 8 | ##### 您的第一个协程程序 9 | 10 | 运行如下代码: 11 | 12 | ```kotlin 13 | fun main(args: Array) { 14 | launch { 15 | // launch new coroutine in background and continue 16 | delay(1000L) // no blocking delay for 1 second (default time unit is ms) 17 | println("World") 18 | } 19 | 20 | println("Hello!") 21 | Thread.sleep(2000L) 22 | } 23 | ``` 24 | 25 | 运行这段代码: 26 | ``` 27 | Hello! 28 | World 29 | ``` 30 | 31 | 实际上,协程是一种轻量级线程。它们和协程生成器一起启动。您可以用 `thread {...}` 替代 `launch {...}` 和使用 `Thread.sleep(...)` 替换 `delay(...)`。您可用尝试一下。 32 | 33 | 如果你开始使用 `thread` 替代 `launch`, 编译器可能会产生下面这个错误 34 | 35 | `Error: Kotlin: Suspend functions are only allowed to be called from a coroutine or another suspend function` 36 | 37 | 这是因为 [delay](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/delay.html) 是一个特殊的正在暂停中的函数,不阻塞线程。但是一个 `suspends coroutine` 只能在协程中被使用。 38 | 39 | 40 | #### 连接阻塞和非阻塞的世界 41 | 第一个例子在主函数的相同代码中混用了非阻塞的 `delay(...)` 和阻塞的 `Thread.sleep`, 很容易疑惑,让我们使用 [runBlocking](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/run-blocking.html) 分离阻塞和非阻塞 42 | ```kotlin 43 | fun main(args: Array) = runBlocking { // start main coroutine 44 | launch { // launch new coroutine 45 | delay(1000L) 46 | println("World!") 47 | } 48 | println("Hello,") // main coroutine continues while child is delayed 49 | delay(2000L) // non-blocking delay for 2 seconds to keep JVM alive 50 | } 51 | ``` 52 | 结果是一样的,但是这段代码只使用了非阻塞的 [delay](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/delay.html) 53 | 54 | `runBlocking {...}` 在这使用是开始一个顶层的主协程的适配器。 `runBlocking` 之外的常规代码阻塞, 直到协程内部的 `runBlocking` 活跃。 55 | 56 | 这也是一个方法去给suspending的函数编写单元测试 57 | ```kotlin 58 | class MyTest { 59 | @Test 60 | fun testMySuspendingFunction() = runBlocking { 61 | // here we can use suspending functions using any assertion style that we like 62 | } 63 | } 64 | ``` 65 | 66 | ### 等待一个任务 67 | 当另一个协程在运行的时候,延时一段时间去等它并不是一个好的办法。我们可以主动等待(非阻塞的)一个后台运行的协程直到它运行完毕。 68 | 69 | ```kotlin 70 | fun main(args: Array) = runBlocking { 71 | val job = launch { // launch new coroutine and keep a reference to its Job 72 | delay(1000L) 73 | println("World!") 74 | } 75 | println("Hello,") 76 | job.join() // wait until child coroutine completes 77 | } 78 | ``` 79 | 80 | 这个结果和上面的实例仍然是一致的。但好多了的是,主协程的代码不需要和后台任务的时间关联起来。 81 | 82 | ### 提取函数进行重构 83 | 让我们把 `launche {...} `里面的代码提取到一个单独的函数里面。当你提取这个单独的函数的时候, 这个新的函数会出现一个新的修饰符 `suspend` 。这是你的第一个 suspend 函数。协程内部可以像一个普通的函数去使用 suspend 函数,相反的,suspend 函数的额外功能就是使用其他的 suspend 函数,比如示例中的 `delay` ,用来暂停执行协程 84 | 85 | ```kotlin 86 | fun main(args: Array) = runBlocking { 87 | val job = launch { doWorld() } 88 | println("Hello,") 89 | job.join() 90 | } 91 | 92 | // this is your first suspending function 93 | suspend fun doWorld() { 94 | delay(1000L) 95 | println("World!") 96 | } 97 | ``` 98 | 99 | ### 协程是轻量级的 100 | 运行下面这段代码: 101 | ```kotlin 102 | fun main(args: Array) = runBlocking { 103 | val jobs = List(100_000) { // launch a lot of coroutines and list their jobs 104 | launch { 105 | delay(1000L) 106 | print(".") 107 | } 108 | } 109 | jobs.forEach { it.join() } // wait for all jobs to complete 110 | } 111 | ``` 112 | 113 | 它启动了 100k 个协程, 1s 后,每个协程会都打印一个点。现在尝试用线程去实现相同的代码,会发生什么呢?(多半会产生 内存溢出 异常) 114 | 115 | 116 | ### 协程像守护线程 117 | 118 | 下面这段代码会启动一个长时间运行的协程, 打印 2 次 “I'm sleeping” 并且在一段延时之后从主协程返回。 119 | ```kotlin 120 | fun main(args: Array) = runBlocking { 121 | launch { 122 | repeat(1000) { i -> 123 | println("I'm sleeping $i ...") 124 | delay(500L) 125 | } 126 | } 127 | delay(1300L) // just quit after delay 128 | } 129 | ``` 130 | 131 | 你会在终端里面看到打印出下面这 3 行: 132 | ``` 133 | I'm sleeping 0 ... 134 | I'm sleeping 1 ... 135 | I'm sleeping 2 ... 136 | ``` 137 | -------------------------------------------------------------------------------- /guide/example/cancelandtimeout.md: -------------------------------------------------------------------------------- 1 | ### 取消和超时 2 | 这一节内容涵盖协程的取消和超时 3 | 4 | ### 取消协程的执行 5 | 在一个小的应用中,从主协程返回,让所有的协程隐式的退出是一个好主意。 6 | 在一个大的并且长时间运行的应用中,你需要更细粒度的控制。一个运行的函数返回一个 Job 可以用来取消一个正在运行的协程。 7 | 8 | ```kotlin 9 | fun main(args: Array) = runBlocking { 10 | val job = launch { 11 | repeat(1000) { i -> 12 | println("I'm sleeping $i ...") 13 | delay(500L) 14 | } 15 | } 16 | delay(1300L) // delay a bit 17 | println("main: I'm tired of waiting!") 18 | job.cancel() // cancels the job 19 | job.join() // waits for job's completion 20 | println("main: Now I can quit.") 21 | } 22 | ``` 23 | 24 | 它的输出如下: 25 | ``` 26 | I'm sleeping 0 ... 27 | I'm sleeping 1 ... 28 | I'm sleeping 2 ... 29 | main: I'm tired of waiting! 30 | main: Now I can quit. 31 | ``` 32 | 33 | 一旦主协程调用了 `job.cancel` ,我们就看不到其他协程的输出了,因为它被取消了。还有一个 [Job](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/index.html) 的扩展函数 [cancelAndJoin](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/cancel-and-join.html) 结合了 [cancel](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/cancel.html) 和 [join](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/join.html)的功能。 34 | 35 | 36 | ### 取消是合作性的 37 | 38 | 协程的取消是合作性的。协程代码必须合作才可以被取消。`kotlinx.coroutines` 包里面所有的 supending 函数都可以被取消。它们检查协程的取消并在取消的时候抛出一个 CancellationException 异常。但是,如果协程正在进行计算并且不检查取消,则不能取消。示例如下: 39 | 40 | ```kotlin 41 | fun main(args: Array) = runBlocking { 42 | val startTime = System.currentTimeMillis() 43 | val job = launch { 44 | var nextPrintTime = startTime 45 | var i = 0 46 | while (i < 5) { // computation loop, just wastes CPU 47 | // print a message twice a second 48 | if (System.currentTimeMillis() >= nextPrintTime) { 49 | println("I'm sleeping ${i++} ...") 50 | nextPrintTime += 500L 51 | } 52 | } 53 | } 54 | delay(1300L) // delay a bit 55 | println("main: I'm tired of waiting!") 56 | job.cancelAndJoin() // cancels the job and waits for its completion 57 | println("main: Now I can quit.") 58 | } 59 | ``` 60 | 61 | 运行它你会发现它在取消后继续打印 "I'm sleeping" 直到任务完成了 5 次迭代。 62 | 63 | ### 使计算的代码可以退出 64 | 65 | 这里有2个方法可以让正在计算的代码取消。第一个是定期的调用 suspending 函数去检查取消。[yield](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/yield.html)函数是一个很好的选择。另一个办法是显示的检查取消状态。让我们试试后面一种方法: 66 | 67 | ```kotlin 68 | fun main(args: Array) = runBlocking { 69 | val startTime = System.currentTimeMillis() 70 | val job = launch { 71 | var nextPrintTime = startTime 72 | var i = 0 73 | while (isActive) { // cancellable computation loop 74 | // print a message twice a second 75 | if (System.currentTimeMillis() >= nextPrintTime) { 76 | println("I'm sleeping ${i++} ...") 77 | nextPrintTime += 500L 78 | } 79 | } 80 | } 81 | delay(1300L) // delay a bit 82 | println("main: I'm tired of waiting!") 83 | job.cancelAndJoin() // cancels the job and waits for its completion 84 | println("main: Now I can quit.") 85 | } 86 | ``` 87 | 88 | 正如你所见,循环终止了。 [isActive](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-scope/is-active.html)是一个属性,可以通过[CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-scope/index.html)在协程的代码内部使用。 89 | 90 | ### 通过 finally 关闭资源 91 | 92 | 取消一个suspending函数抛出了一个 CancellationException , 在取消的时候可以用统一的方法去处理。比如,函数在协程被取消时通常在 `try {...} finally {...}` 表达式 和 koltin `use` 中执行最终的行为 93 | 94 | ```kotlin 95 | fun main(args: Array) = runBlocking { 96 | val job = launch { 97 | try { 98 | repeat(1000) { i -> 99 | println("I'm sleeping $i ...") 100 | delay(500L) 101 | } 102 | } finally { 103 | println("I'm running finally") 104 | } 105 | } 106 | delay(1300L) // delay a bit 107 | println("main: I'm tired of waiting!") 108 | job.cancelAndJoin() // cancels the job and waits for its completion 109 | println("main: Now I can quit.") 110 | } 111 | ``` 112 | 113 | join和cancelAnd都等待所有完成操作完成,因此上面的示例会生成以下输出: 114 | ``` 115 | I'm sleeping 0 ... 116 | I'm sleeping 1 ... 117 | I'm sleeping 2 ... 118 | main: I'm tired of waiting! 119 | I'm running finally 120 | main: Now I can quit. 121 | ``` 122 | 123 | ### 运行不可取消的代码块 124 | 125 | 任何在前面例子的 finally 块中使用 suspending 函数都会导致 CancellationException,因为运行这个代码的协程被取消。通常情况下这都不是问题,因为所有的关闭操作(关闭文件、取消任务或者关闭任何类型的通信通道)都是非阻塞的,并且不涉及任何 suspending 函数。但是,在极少数情况下,如果需要在取消的协程中暂停,可以使用 [run]() 函数和 NonCancellable 上下文 将相应的代码封装在 `run(NonCancellable) {...}` 中, 就像下面的实例一样: 126 | 127 | ```kotlin 128 | fun main(args: Array) = runBlocking { 129 | val job = launch { 130 | try { 131 | repeat(1000) { i -> 132 | println("I'm sleeping $i ...") 133 | delay(500L) 134 | } 135 | } finally { 136 | run(NonCancellable) { 137 | println("I'm running finally") 138 | delay(1000L) 139 | println("And I've just delayed for 1 sec because I'm non-cancellable") 140 | } 141 | } 142 | } 143 | delay(1300L) // delay a bit 144 | println("main: I'm tired of waiting!") 145 | job.cancelAndJoin() // cancels the job and waits for its completion 146 | println("main: Now I can quit.") 147 | } 148 | ``` 149 | 150 | ### 超时 151 | 在实践中, 我们有更明显的理由去取消一个协程的运行。因为它的执行超出了某个时间。虽然您可用手动跟踪相应的任务的引用,并启用一个单独的协程在延迟后取消跟踪。但是可以使用[withTimeout](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/with-timeout.html)函数来执行此操作。看接下来的例子: 152 | 153 | ```kotlin 154 | fun main(args: Array) = runBlocking { 155 | withTimeout(1300L) { 156 | repeat(1000) { i -> 157 | println("I'm sleeping $i ...") 158 | delay(500L) 159 | } 160 | } 161 | } 162 | ``` 163 | 164 | 它产生以下输出: 165 | ``` 166 | I'm sleeping 0 ... 167 | I'm sleeping 1 ... 168 | I'm sleeping 2 ... 169 | Exception in thread "main" kotlinx.coroutines.experimental.TimeoutCancellationException: Timed out waiting for 1300 MILLISECONDS 170 | ``` 171 | 172 | `TimeoutCancellationException` 是一个被 [withTimeout](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/with-timeout.html) 抛出的 [CancellationException]() 子类。我们之前没有在控制台上看到它打印堆栈,这是因为在一个取消的协程中,`CancellationException` 是一个正常的协程完成的原因。 但是在这个例子中,我们已经在主协程中使用了 `withTimeout` 173 | 174 | 因为取消只是一个表达式,通常情况下所有的资源都会关闭,你可以在 `try {...} catch (e: TimeoutCancellationException) {...}` 代码块中使用timeout包装代码, 如果你需要针对任何超时类型做一些额外的特殊操作,或者使用类似 [withTimeout](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/with-timeout.html)的 [withTimeoutOrNull](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/with-timeout-or-null.html) 函数,但是在超时的时候返回null,而不是抛出异常。 175 | 176 | ```kotlin 177 | fun main(args: Array) = runBlocking { 178 | val result = withTimeoutOrNull(1300L) { 179 | repeat(1000) { i -> 180 | println("I'm sleeping $i ...") 181 | delay(500L) 182 | } 183 | "Done" // will get cancelled before it produces this result 184 | } 185 | println("Result is $result") 186 | } 187 | ``` 188 | 189 | 这段代码运行的时候,将不再会有一个异常: 190 | ``` 191 | I'm sleeping 0 ... 192 | I'm sleeping 1 ... 193 | I'm sleeping 2 ... 194 | Result is null 195 | ``` 196 | -------------------------------------------------------------------------------- /guide/example/channel.md: -------------------------------------------------------------------------------- 1 | ### Channels 2 | 延迟值提供了在协程之间传递单个值的便捷方法。Channels 提供了一种方式来传输流式值 3 | 4 | ### Channel基础 5 | 6 | 一个 [Channel](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-channel/index.html) 在概念上和 `BlockingQueue` 非常相似。 一个关键的不同点是使用了一个可挂起的 [send](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-send-channel/send.html) 替代阻塞的 `put` 操作, 使用了一个可挂起的 [receive](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-receive-channel/receive.html) 替代阻塞的 `take` 操作。 7 | 8 | ```kotlin 9 | fun main(args: Array) = runBlocking { 10 | val channel = Channel() 11 | launch { 12 | // this might be heavy CPU-consuming computation or async logic, we'll just send five squares 13 | for (x in 1..5) channel.send(x * x) 14 | } 15 | // here we print five received integers: 16 | repeat(5) { println(channel.receive()) } 17 | println("Done!") 18 | } 19 | ``` 20 | 21 | 代码输出如下 22 | ``` 23 | 1 24 | 4 25 | 9 26 | 16 27 | 25 28 | Done! 29 | ``` 30 | 31 | ### 关闭和遍历 channels 32 | 33 | 和队列不太像的是,一个 channel 可以被关闭,以表示没有更多的元素进入。在接受端可以很方便的定期使用 `for` 循环从 channel 中收取元素。 34 | 35 | 在概念上,一个 [close](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-send-channel/close.html) 就像挂起了一个专门的 channel 关闭令牌。当收到关闭令牌后,迭代会尽快停止,所以这可以保证之前发送的元素在 channel 关闭之前被全部接收。 36 | 37 | ```kotlin 38 | fun main(args: Array) = runBlocking { 39 | val channel = Channel() 40 | launch { 41 | for (x in 1..5) channel.send(x * x) 42 | channel.close() // we're done sending 43 | } 44 | // here we print received values using `for` loop (until the channel is closed) 45 | for (y in channel) println(y) 46 | println("Done!") 47 | } 48 | ``` 49 | 50 | ### 创建channel的生产者 51 | 协程产生一系列元素的模式很常见,这是 生产者-消费者 模式的一部分,你可以把生产者抽象成一个把channel作为参数的函数,但这常识相反,函数必须返回一个结果。 52 | 53 | 这有一个非常方便的协程生产者叫做 [produce](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/produce.html), 它非常容易正确的作为一个生产者。还有一个扩展函数 [consumeEach](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/consume-each.html),在消费者端替换 `for` 循环。 54 | 55 | ```kotlin 56 | fun produceSquares() = produce { 57 | for (x in 1..5) send(x * x) 58 | } 59 | 60 | fun main(args: Array) = runBlocking { 61 | val squares = produceSquares() 62 | squares.consumeEach { println(it) } 63 | println("Done!") 64 | } 65 | ``` 66 | 67 | ### 管道 68 | 管道是一个模式,一个协程作为生产者,可能是无限的流 69 | 70 | ```kotlin 71 | fun produceNumbers() = produce { 72 | var x = 1 73 | while (true) send(x++) // infinite stream of integers starting from 1 74 | } 75 | ``` 76 | 77 | 另一个协程作为流的消费者,做一些处理,并且生产一些其他的结果。下面的例子消费者只是计算了数字的平方: 78 | 79 | ```kotlin 80 | fun square(numbers: ReceiveChannel) = produce { 81 | for (x in numbers) send(x * x) 82 | } 83 | ``` 84 | 85 | main函数的代码将管道连接: 86 | ```kotlin 87 | fun main(args: Array) = runBlocking { 88 | val numbers = produceNumbers() // produces integers from 1 and on 89 | val squares = square(numbers) // squares integers 90 | for (i in 1..5) println(squares.receive()) // print first five 91 | println("Done!") // we are done 92 | squares.cancel() // need to cancel these coroutines in a larger app 93 | numbers.cancel() 94 | } 95 | ``` 96 | 在这个实例程序中我们没有取消这些协程,因为协程 [就像守护线程一样](https://github.com/Kotlin/kotlinx.coroutines/blob/379f210f1d6f6ee91d6198348bd16306c93153ce/coroutines-guide.md#coroutines-are-like-daemon-threads),但是在一个大型应用中,如果我们不使用了,我们需要停止我们的管道。我们可以把管道作为 [main协程的子协程](https://github.com/Kotlin/kotlinx.coroutines/blob/379f210f1d6f6ee91d6198348bd16306c93153ce/coroutines-guide.md#children-of-a-coroutine), 下面的例子演示了这个: 97 | 98 | ### 管道实现素数 99 | 100 | 让我们使用协程构造一个比较极端的示例,使用一个管道协程生成素数。 我们使用一个无穷序列开始。这时候我们介绍一个显示的 `context` 参数,并且通过它去调度 [produce](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/produce.html) 构造器,因此,调用者可以控制我们的协程在哪里运行: 101 | 102 | ```kotlin 103 | fun numbersFrom(context: CoroutineContext, start: Int) = produce(context) { 104 | var x = start 105 | while (true) send(x++) // infinite stream of integers from start 106 | } 107 | ``` 108 | 以下管道阶段过滤传入的数据,去除所有给定的素数整除的数字: 109 | ```kotlin 110 | fun filter(context: CoroutineContext, numbers: ReceiveChannel, prime: Int) = produce(context) { 111 | for (x in numbers) if (x % prime != 0) send(x) 112 | } 113 | ``` 114 | 115 | 现在我们建立管道从 2 开始一连串的数字, 从当前频道质数, 并推出新的管道阶段为每个素数的发现: 116 | 117 | ``` 118 | numbersFrom(2) -> filter(2) -> filter(3) -> filter(5) -> filter(7) ... 119 | ``` 120 | 121 | 下面的示例输出第十个质数, 整个管道运行在主线程中。所有的协程都作为主协程使用 [corountinContext](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-scope/coroutine-context.html) 调度的[runBlocking](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/run-blocking.html) 去执行。我们不想保持所有我们启动的协程的列表。我们使用 [cancelChildren](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/kotlin.coroutines.experimental.-coroutine-context/cancel-children.html) 扩展函数去取消所有的子协程。 122 | 123 | ```kotlin 124 | fun main(args: Array) = runBlocking { 125 | var cur = numbersFrom(coroutineContext, 2) 126 | for (i in 1..10) { 127 | val prime = cur.receive() 128 | println(prime) 129 | cur = filter(coroutineContext, cur, prime) 130 | } 131 | coroutineContext.cancelChildren() // cancel all children to let main finish 132 | } 133 | ``` 134 | 135 | 代码输出如下: 136 | 137 | ```kotlin 138 | 2 139 | 3 140 | 5 141 | 7 142 | 11 143 | 13 144 | 17 145 | 19 146 | 23 147 | 29 148 | ``` 149 | 150 | 需要注意的,你可以使用标准库的 [buildIterator](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/build-iterator.html) 协程创建器创建一些管道。`buildIterator` 替代 `produce` , `yield` 替代 `send`, `next` 替换 `receive`, `Iterator` 替换 `ReceiveChannel`, 并且摆脱上下文。你也不再需要 `runBlocking`。然而, 管道的好处是如果你用 [CommonPool](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-common-pool/index.html) 按照如上方式使用 channel 实际上可以使用多核 CPU。 151 | 152 | 总之,这是一个非常不切实际的方式寻找质数。在实践中,管道调用其他的挂起操作(例如异步调用远程服务),并且这些管道不能使用`buildSeqeunce` / `buildIterator` 创建,因为他们不允许任意挂起。不像 `produce`,是完全异步的。 153 | 154 | ### 扇出 155 | 多个协程可能会从遇到从一个 channel 获取数据以及它们之间的分配工作,让我们从一个生产者协程开始,周期性的产生整数(每秒十个数字) 156 | 157 | ```kotlin 158 | fun produceNumbers() = produce { 159 | var x = 1 // start from 1 160 | while (true) { 161 | send(x++) // produce next 162 | delay(100) // wait 0.1s 163 | } 164 | } 165 | ``` 166 | 167 | 然后我们可以有多个处理器协同程序。在这个例子中,他们只是打印自己的id和收到的数字: 168 | 169 | ```kotlin 170 | fun launchProcessor(id: Int, channel: ReceiveChannel) = launch { 171 | channel.consumeEach { 172 | println("Processor #$id received $it") 173 | } 174 | } 175 | ``` 176 | 现在我们启动五个处理程序几乎让他们同时工作,看看会发生什么: 177 | ```kotlin 178 | fun main(args: Array) = runBlocking { 179 | val producer = produceNumbers() 180 | repeat(5) { launchProcessor(it, producer) } 181 | delay(950) 182 | producer.cancel() // cancel producer coroutine and thus kill them all 183 | } 184 | ``` 185 | 186 | 输出将类似于下面的一个,尽管处理器接受每个特定整数id可能会有所不同: 187 | 188 | ``` 189 | Processor #2 received 1 190 | Processor #4 received 2 191 | Processor #0 received 3 192 | Processor #1 received 4 193 | Processor #3 received 5 194 | Processor #2 received 6 195 | Processor #4 received 7 196 | Processor #0 received 8 197 | Processor #1 received 9 198 | Processor #3 received 10 199 | ``` 200 | 201 | 注意,取消一个生产者协程关闭 channel,因此最终协同程序正在做的事情是终止迭代信道处理器。 202 | 203 | ### 扇入 204 | 多个协同程序可能发送到相同的频道。例如, 让我们有一个字符串, 渠道和暂停功能, 往这个 channel 反复发送一个指定字符串指定的延迟 205 | 206 | ```kotlin 207 | suspend fun sendString(channel: SendChannel, s: String, time: Long) { 208 | while (true) { 209 | delay(time) 210 | channel.send(s) 211 | } 212 | } 213 | ``` 214 | 215 | 现在,让我们看看如果我们启动一些发送字符串的协程,会发生什么情况(在本例中,我们将它们作为主协程的子节点在主线程的上下文中启动): 216 | 217 | ```kotlin 218 | fun main(args: Array) = runBlocking { 219 | val channel = Channel() 220 | launch(coroutineContext) { sendString(channel, "foo", 200L) } 221 | launch(coroutineContext) { sendString(channel, "BAR!", 500L) } 222 | repeat(6) { // receive first six 223 | println(channel.receive()) 224 | } 225 | coroutineContext.cancelChildren() // cancel all children to let main finish 226 | } 227 | ``` 228 | 229 | 输出如下: 230 | 231 | ``` 232 | foo 233 | foo 234 | BAR! 235 | foo 236 | foo 237 | BAR! 238 | ``` 239 | 240 | ### 缓冲channel 241 | 目前为止还没显式的带缓冲 channel,没有缓冲的 channel 转移元素的时候,发送方和接受方相见(又叫对接)。如果发送调用,那么它将被暂停直到接收方调用。如果接收方接受了第一个元素,它会暂停,直到发送方调用。 242 | 243 | [Channel()](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-channel.html) 和 [produce](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/produce.html)构造器都有一个可选的 `capacity` 参数去定义缓冲的大小。缓冲允许发送方在挂起之前发送多个元素,就像 `BlockingQueue` 有一个指定的容量一样,当缓冲区满了将会阻塞。 244 | 245 | 看一下下面代码的行为: 246 | ```kotlin 247 | fun main(args: Array) = runBlocking { 248 | val channel = Channel(4) // create buffered channel 249 | val sender = launch(coroutineContext) { // launch sender coroutine 250 | repeat(10) { 251 | println("Sending $it") // print before sending each element 252 | channel.send(it) // will suspend when buffer is full 253 | } 254 | } 255 | // don't receive anything... just wait.... 256 | delay(1000) 257 | sender.cancel() // cancel sender coroutine 258 | } 259 | ``` 260 | 261 | 它可以使用四个容量的缓冲 channel 打印 sending 五次。 262 | 263 | 发送方发送的前四个元素存在了channel的缓冲区,并且在尝试发送第五个元素的时候发送方被挂起。 264 | 265 | ### channel是公平的 266 | 267 | 对于多协程的调用顺序来说, channel 的发送和接受操作是公平的。它们都是先进先出,例如,第一个协程调用 `receive` 去获取元素。在下面的示例中,2个协程 ping 和 pong 从共享的 "table" channel 中 接收 ball 对象 268 | 269 | ```kotlin 270 | data class Ball(var hits: Int) 271 | 272 | fun main(args: Array) = runBlocking { 273 | val table = Channel() // a shared table 274 | launch(coroutineContext) { player("ping", table) } 275 | launch(coroutineContext) { player("pong", table) } 276 | table.send(Ball(0)) // serve the ball 277 | delay(1000) // delay 1 second 278 | coroutineContext.cancelChildren() // game over, cancel them 279 | } 280 | 281 | suspend fun player(name: String, table: Channel) { 282 | for (ball in table) { // receive the ball in a loop 283 | ball.hits++ 284 | println("$name $ball") 285 | delay(300) // wait a bit 286 | table.send(ball) // send the ball back 287 | } 288 | } 289 | ``` 290 | 291 | ping 协程首先启动,因此它是第一个接收 ball 的。 尽管 ping 协同程序在发回 ball 后立即开始接收 ball,但 ball 被 ping pong 协程接收,因为它已经在等待 ball: 292 | 293 | ``` 294 | ping Ball(hits=1) 295 | pong Ball(hits=2) 296 | ping Ball(hits=3) 297 | pong Ball(hits=4) 298 | ``` 299 | 300 | 注意,由于执行程序所使用的性质, 有时 channel 可能会产生执行看起来不公平。有关详细信息,请参阅这一问题 [issus](https://github.com/Kotlin/kotlinx.coroutines/issues/111)。 -------------------------------------------------------------------------------- /guide/example/componsecoroutine.md: -------------------------------------------------------------------------------- 1 | ### 组合 suspending 函数 2 | 3 | 本节涵盖了组合 suspending 函数的几种方法 4 | 5 | ### 按默认顺序 6 | 7 | 假设我们有 2 个在别处定义的有用的 suspending 函数,例如请求远程服务或者运算,我们只是假装它有用,但实际上每个函数只是为了本例的目的延迟 1 秒: 8 | 9 | ```kotlin 10 | suspend fun doSomethingUsefulOne(): Int { 11 | delay(1000L) // pretend we are doing something useful here 12 | return 13 13 | } 14 | 15 | suspend fun doSomethingUsefulTwo(): Int { 16 | delay(1000L) // pretend we are doing something useful here, too 17 | return 29 18 | } 19 | ``` 20 | 21 | 如果需要依次调用它们,我们应该如何做呢?-- 首先运行 `doSomethingUsefulOne` 如何运行 `doSomethingUsefulTwo`,如何计算它们的总和?实际上如果我们通过第一个函数的结果来决定是否需要调用第二个函数或者决定如何调用它,我们会这样做。 22 | 23 | 我们只是用正常的顺序去调用,因为协程中的代码和普通代码中的代码一样,默认情况下是顺序执行的。以下实例是通过衡量两个suspending 函数所需要的总时间的演示: 24 | 25 | ```kotlin 26 | fun main(args: Array) = runBlocking { 27 | val time = measureTimeMillis { 28 | val one = doSomethingUsefulOne() 29 | val two = doSomethingUsefulTwo() 30 | println("The answer is ${one + two}") 31 | } 32 | println("Completed in $time ms") 33 | } 34 | ``` 35 | 36 | 它产生如下结果: 37 | ``` 38 | The answer is 42 39 | Completed in 2017 ms 40 | ``` 41 | 42 | ### 使用 async 并发 43 | 44 | 如果doSomethingUsefulOne和doSomethingUsefulTwo的调用之间没有依赖关系,并且我们希望通过同时执行这两者来更快地获得答案,那该怎么办?这时候 [async](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/async.html) 就来帮我们了。 45 | 46 | 从概念上,[async](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/async.html) 就像是 [launch](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/launch.html),它启动一个单独的协程,它是一个和其他协程同时工作的轻量级线程。不同点在于 `launch` 返回一个 [Job](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/index.html) 并且不会有任何结果, 而 `async` 返回一个 [Deferred](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html) -- 这是一个轻量级的无阻塞未来,它代表了稍后提供结果的承诺。 您可以在一个延迟的值上使用`.await()` 去获取它最终的结果,但是 `Deferred` 仍然是一个 `Job`,如果你需要,你可以取消它。 47 | 48 | ```kotlin 49 | fun main(args: Array) = runBlocking { 50 | val time = measureTimeMillis { 51 | val one = async { doSomethingUsefulOne() } 52 | val two = async { doSomethingUsefulTwo() } 53 | println("The answer is ${one.await() + two.await()}") 54 | } 55 | println("Completed in $time ms") 56 | } 57 | ``` 58 | 59 | 它产生如下结果: 60 | 61 | ``` 62 | The answer is 42 63 | Completed in 1017 ms 64 | ``` 65 | 66 | 这是两倍的速度, 因为我们同时执行 2 个协程程序。请注意, 协程的并发总是显示的。 67 | 68 | ### 惰性启动async 69 | 在使用 `async` 的时候有一个惰性选项,将 [CoroutineStart.LAZY](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-start/-l-a-z-y.html) 作为 `start` 的参数值。它只在结果需要被[await](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/await.html) 或者一个 [start](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/start.html) 函数被调用的时候启动一个协程。运行下例代码,和以前不同的是,现在只通过这个选项。 70 | 71 | ```kotlin 72 | fun main(args: Array) = runBlocking { 73 | val time = measureTimeMillis { 74 | val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() } 75 | val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() } 76 | println("The answer is ${one.await() + two.await()}") 77 | } 78 | println("Completed in $time ms") 79 | } 80 | ``` 81 | 82 | 它产生如下结果: 83 | ``` 84 | The answer is 42 85 | Completed in 2017 ms 86 | ``` 87 | 88 | 89 | 所以,我们回到了顺序执行,因为我们开始等待 `one` , 然后开始等待 `two`。这并不是惰性的预期用例。它被设计为计算涉及暂停函数的情况下替代标准的 `lazy` 函数。 90 | 91 | ### Async 风格的函数 92 | 93 | 我们可以使用 [async](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/async.html) 构造协程,d定义 async 风格的函数去异步的调用 `doSomethingUsefulOne` 和 `doSomethingUsefulTwo`。以“async” 前缀或者 “Async”后缀命名的函数是一个很好的风格,突出显示它们只启动异步计算的事实,并且需要使用得到的延迟值获取结果。 94 | 95 | ```kotlin 96 | // The result type of asyncSomethingUsefulOne is Deferred 97 | fun asyncSomethingUsefulOne() = async { 98 | doSomethingUsefulOne() 99 | } 100 | 101 | // The result type of asyncSomethingUsefulTwo is Deferred 102 | fun asyncSomethingUsefulTwo() = async { 103 | doSomethingUsefulTwo() 104 | } 105 | ``` 106 | 107 | 注意,这些 `asyncXXX` 函数并不是 `suspending`函数。它们可以在任何地方被使用。然而,它们的使用总是按时它们的行为和代码调用异步(这里指并发)执行。 108 | 109 | 以下示例显示了它们在协程之外的用法 110 | 111 | ```kotlin 112 | // note, that we don't have `runBlocking` to the right of `main` in this example 113 | fun main(args: Array) { 114 | val time = measureTimeMillis { 115 | // we can initiate async actions outside of a coroutine 116 | val one = asyncSomethingUsefulOne() 117 | val two = asyncSomethingUsefulTwo() 118 | // but waiting for a result must involve either suspending or blocking. 119 | // here we use `runBlocking { ... }` to block the main thread while waiting for the result 120 | runBlocking { 121 | println("The answer is ${one.await() + two.await()}") 122 | } 123 | } 124 | println("Completed in $time ms") 125 | } 126 | ``` 127 | -------------------------------------------------------------------------------- /guide/example/contextdispatchers.md: -------------------------------------------------------------------------------- 1 | ### 协程上下文和调度器 2 | 3 | 协程总是运行在一些定义在 kotlin 标准库中,以 [CoroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/-coroutine-context/)类型为代表的上下文中。 4 | 5 | 协程的上下文是一个各种元素的集合。主要的元素是协程的 [Job](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/index.html),我们将在这节介绍它的调度器。 6 | 7 | ### 调度器和线程 8 | 9 | 协程上下文包括一个协程调度器(见 [CoroutineDispatcher](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-dispatcher/index.html)),这个调度器决定了相应的协程运行在哪个线程(也可能是多个线程)。协程调度器可以限定协程运行在一个特定的线程,或者分派到一个线程池,或者让它不受限制的运行。 10 | 11 | 所有的像 [launch](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/launch.html) 和 [async](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/async.html) 一样协程构造器接受一个可选的 [CoroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/-coroutine-context/) 参数可以用来显示的为新协程或者其他上下文元素指定调度器。 12 | 13 | 尝试下面的例子: 14 | 15 | ```kotlin 16 | fun main(args: Array) = runBlocking { 17 | val jobs = arrayListOf() 18 | jobs += launch(Unconfined) { // not confined -- will work with main thread 19 | println(" 'Unconfined': I'm working in thread ${Thread.currentThread().name}") 20 | } 21 | jobs += launch(coroutineContext) { // context of the parent, runBlocking coroutine 22 | println("'coroutineContext': I'm working in thread ${Thread.currentThread().name}") 23 | } 24 | jobs += launch(CommonPool) { // will get dispatched to ForkJoinPool.commonPool (or equivalent) 25 | println(" 'CommonPool': I'm working in thread ${Thread.currentThread().name}") 26 | } 27 | jobs += launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread 28 | println(" 'newSTC': I'm working in thread ${Thread.currentThread().name}") 29 | } 30 | jobs.forEach { it.join() } 31 | } 32 | ``` 33 | 34 | 它产生如下输出: 35 | ``` 36 | 'Unconfined': I'm working in thread main 37 | 'CommonPool': I'm working in thread ForkJoinPool.commonPool-worker-1 38 | 'newSTC': I'm working in thread MyOwnThread 39 | 'coroutineContext': I'm working in thread main 40 | ``` 41 | 42 | 我们在前几节使用的默认调度器由 [DefaultDispatcher](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-default-dispatcher.html)代表,这个和当前实现的 [CommonPool](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-common-pool/index.html) 相同,所以 `launch {...}` 和 `launch(DefaultDispather) {...}` 一样,和 `launch(CommonPool) {...}` 也一样。 43 | 44 | 父 [coroutineContext](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-scope/coroutine-context.html) 和 [Unconfined](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-unconfined/index.html) 的区别会在稍后展示。 45 | 46 | 注意,[newSingleThreadContext](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/new-single-thread-context.html) 创建了一个新的线程,这是非常耗费资源的。在实际的应用中,当他它不再需要的时候,它必须被释放,使用 [close](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-thread-pool-dispatcher/close.html) 函数,或者存储在一个底层变量让整个应用重用。 47 | 48 | ### 无限制的和有限制的调度器 49 | 50 | [Unconfined](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-unconfined/index.html) 协程调度器在调用者线程中启动,但是只到第一个暂停点。暂停后在由被调用的暂停功能完全确定的线程中恢复。当协程没有耗费 CPU 时间或者没有更新任何局限在特定线程的共享数据(例如 UI),无限制的调度器是合适的。 51 | 52 | 另一方面,[coroutineContext](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-scope/coroutine-context.html) 属性在通过 [CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-scope/index.html) 接口在任何协程块中使用,是对这个特定协程的上下文的引用。通过这个方法,一个父上下文可以被继承。[runBlocking](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/run-blocking.html) 协程默认的调度器,尤其是仅限于调用者线程的,所以继承它具有将执行限制在具有可预测的FIFO调度的该线程的效果。 53 | 54 | ```kotlin 55 | fun main(args: Array) = runBlocking { 56 | val jobs = arrayListOf() 57 | jobs += launch(Unconfined) { 58 | // not confined -- will work with main thread 59 | println(" 'Unconfined': I'm working in thread ${Thread.currentThread().name}") 60 | delay(500) 61 | println(" 'Unconfined': After delay in thread ${Thread.currentThread().name}") 62 | } 63 | jobs += launch(coroutineContext) { 64 | // context of the parent, runBlocking coroutine 65 | println("'coroutineContext': I'm working in thread ${Thread.currentThread().name}") 66 | delay(1000) 67 | println("'coroutineContext': After delay in thread ${Thread.currentThread().name}") 68 | } 69 | jobs.forEach { it.join() } 70 | } 71 | 72 | ``` 73 | 74 | 会产生一下输出: 75 | ``` 76 | 'Unconfined': I'm working in thread main 77 | 'coroutineContext': I'm working in thread main 78 | 'Unconfined': After delay in thread kotlinx.coroutines.DefaultExecutor 79 | 'coroutineContext': After delay in thread main 80 | ``` 81 | 82 | 所以,继承了 `runBlocking {...}` 的 `coroutineContext` 协程运行在 `main` 线程,不受限的协程在使用了 [delay](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/delay.html) 的时候在默认的执行者线程中恢复。 83 | 84 | ### 调试协程和线程 85 | 86 | 在使用 [Unconfined](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-unconfined/index.html) 调度器 或者 使用默认的多线程调度器的时候,协程是可以暂停并且在另一个线程恢复的。即使在单线程的调度器中,也是非常不容易去判断协程什么时候,在什么地方,正在做什么事情。一个公共的多线程下的调试方法就是在每一句log的代码里面去打印当前的线程名称。这个功能在日志框架中是普遍支持的。 当使用协程的时候,单独的线程名是不会给出很多上下文。所以 `kotlinx.coroutines` 包括了调试设备让调试协程更加容易。 87 | 88 | 使用 `-Dkotlinx.coroutines.debug` jvm参数运行如下代码: 89 | ```kotlin 90 | fun log(msg: String) = println("[${Thread.currentThread().name}] $msg") 91 | 92 | fun main(args: Array) = runBlocking { 93 | val a = async(coroutineContext) { 94 | log("I'm computing a piece of the answer") 95 | 6 96 | } 97 | val b = async(coroutineContext) { 98 | log("I'm computing another piece of the answer") 99 | 7 100 | } 101 | log("The answer is ${a.await() * b.await()}") 102 | } 103 | ``` 104 | 105 | 这里有3个协程,主协程(#1) -- 一个是 `runBlocking`, 其它 2 个协程计算延迟的值 a(#2) 和 b(#3)。它们都运行在 `runBlocking` 的上下文,并且被主线程控制。输出的代码如下: 106 | 107 | ``` 108 | [main @coroutine#2] I'm computing a piece of the answer 109 | [main @coroutine#3] I'm computing another piece of the answer 110 | [main @coroutine#1] The answer is 42 111 | ``` 112 | 113 | 你可以看到 `log` 功能在方括号中打印线程的名称。那是 `main` 线程。当时当前运行的协程的识别码附加到了它上面。在打开调试模式时,此标识符将连续分配给所有创建的协同程序。 114 | 115 | 您可以在 [newCoroutineContext](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/new-coroutine-context.html)的文档上阅读更多关于调试设备的内容。 116 | 117 | ### 在线程之间跳转 118 | 119 | 使用 `-Dkotlinx.coroutines.debug` jvm参数运行如下代码: 120 | 121 | ```kotlin 122 | fun log(msg: String) = println("[${Thread.currentThread().name}] $msg") 123 | 124 | fun main(args: Array) { 125 | newSingleThreadContext("Ctx1").use { ctx1 -> 126 | newSingleThreadContext("Ctx2").use { ctx2 -> 127 | runBlocking(ctx1) { 128 | log("Started in ctx1") 129 | run(ctx2) { 130 | log("Working in ctx2") 131 | } 132 | log("Back to ctx1") 133 | } 134 | } 135 | } 136 | } 137 | ``` 138 | 139 | 这演示了几项新技术,一个是使用 [runBlocking](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/run-blocking.html) 明确指定上下文, 另一个是使用 [run]() 函数去改变协程的上下文,同时仍然保留在相同的协程中,您可以在下面的输出中看到: 140 | ``` 141 | [Ctx1 @coroutine#1] Started in ctx1 142 | [Ctx2 @coroutine#1] Working in ctx2 143 | [Ctx1 @coroutine#1] Back to ctx1 144 | ``` 145 | 146 | 注意,那个例子也使用了 `use` 函数在不再需要使用 [newSingleThreadContext](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/new-single-thread-context.html) 创建的时候,去释放线程。 147 | 148 | ### 上下文中的Job 149 | 150 | 协程的 [job](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/index.html) 是上下文的一部分,协程可以使用 `coroutineContext[Job]` 表达式从它自己的上下文中检索它: 151 | 152 | ```kotlin 153 | fun main(args: Array) = runBlocking { 154 | println("My job is ${coroutineContext[Job]}") 155 | } 156 | ``` 157 | 158 | 在 debug model 下会产生如下输出: 159 | 160 | ``` 161 | My job is "coroutine#1":BlockingCoroutine{Active}@6d311334 162 | ``` 163 | 164 | 所以, [CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-scope/index.html) 的[isActive](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-scope/is-active.html) 只是 `coroutineContext[Job]!!.isActive.` 的一个方便途径。 165 | 166 | ### 子协程 167 | 当一个协程的 [coroutineContext](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-scope/coroutine-context.html) 被用来启动另一个协程,新的协程的 [Job](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/index.html) 成了父协程的任务的一个孩子。当一个父协程被取消的时候,它的所以的子协程也会被递归的取消。 168 | 169 | ```kotlin 170 | fun main(args: Array) = runBlocking { 171 | // launch a coroutine to process some kind of incoming request 172 | val request = launch { 173 | // it spawns two other jobs, one with its separate context 174 | val job1 = launch { 175 | println("job1: I have my own context and execute independently!") 176 | delay(1000) 177 | println("job1: I am not affected by cancellation of the request") 178 | } 179 | // and the other inherits the parent context 180 | val job2 = launch(coroutineContext) { 181 | println("job2: I am a child of the request coroutine") 182 | delay(1000) 183 | println("job2: I will not execute this line if my parent request is cancelled") 184 | } 185 | // request completes when both its sub-jobs complete: 186 | job1.join() 187 | job2.join() 188 | } 189 | delay(500) 190 | request.cancel() // cancel processing of the request 191 | delay(1000) // delay a second to see what happens 192 | println("main: Who has survived request cancellation?") 193 | } 194 | ``` 195 | 196 | 这段代码的输出是: 197 | ``` 198 | job1: I have my own context and execute independently! 199 | job2: I am a child of the request coroutine 200 | job1: I am not affected by cancellation of the request 201 | main: Who has survived request cancellation? 202 | ``` 203 | 204 | ### 合并上下文 205 | 协程上下文可以使用`+`操作符合并。右边的上下文取代左边上下文的条目。举个例子,父协程的任务可以被继承,而他的调度员被取代。 206 | 207 | ```kotlin 208 | fun main(args: Array) = runBlocking { 209 | // start a coroutine to process some kind of incoming request 210 | val request = launch(coroutineContext) { // use the context of `runBlocking` 211 | // spawns CPU-intensive child job in CommonPool !!! 212 | val job = launch(coroutineContext + CommonPool) { 213 | println("job: I am a child of the request coroutine, but with a different dispatcher") 214 | delay(1000) 215 | println("job: I will not execute this line if my parent request is cancelled") 216 | } 217 | job.join() // request completes when its sub-job completes 218 | } 219 | delay(500) 220 | request.cancel() // cancel processing of the request 221 | delay(1000) // delay a second to see what happens 222 | println("main: Who has survived request cancellation?") 223 | } 224 | ``` 225 | 226 | 代码预期的输出是: 227 | ``` 228 | job: I am a child of the request coroutine, but with a different dispatcher 229 | main: Who has survived request cancellation? 230 | ``` 231 | 232 | 233 | ### 父协程的职责 234 | 一个父协程总是等待它所有的子协程完成。父协程不需要显式的跟踪子协程的启动,并且它不需要去使用子协程的 [Job.join](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/join.html) 等待它们。 235 | 236 | ```kotlin 237 | fun main(args: Array) = runBlocking { 238 | // launch a coroutine to process some kind of incoming request 239 | val request = launch { 240 | repeat(3) { i -> // launch a few children jobs 241 | launch(coroutineContext) { 242 | delay((i + 1) * 200L) // variable delay 200ms, 400ms, 600ms 243 | println("Coroutine $i is done") 244 | } 245 | } 246 | println("request: I'm done and I don't explicitly join my children that are still active") 247 | } 248 | request.join() // wait for completion of the request, including all its children 249 | println("Now processing of the request is complete") 250 | } 251 | ``` 252 | 253 | 结果如下: 254 | ``` 255 | request: I'm done and I don't explicitly join my children that are still active 256 | Coroutine 0 is done 257 | Coroutine 1 is done 258 | Coroutine 2 is done 259 | Now processing of the request is complete 260 | ``` 261 | 262 | ### 命名协程用以调试 263 | 当 log 经常使用在 coroutine 的时候,自动分配一个id是非常棒的主意。你只需要关联来自于同一个协程的日志记录。然而,当协程和特定请求的处理或者和执行一些特殊的后台任务相关联的时候,最好拥有一个显式的名字以确保调试的目的明确。 [CoroutineName](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-name/index.html) 上下文元素作为一个线程的名字提供相同的功能。当 [调试模式](https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md#debugging-coroutines-and-threads) 打开的时候,他会显示在执行此协程的线程名字中。 264 | 265 | 下面的例子演示了这一概念: 266 | 267 | ```kotlin 268 | fun log(msg: String) = println("[${Thread.currentThread().name}] $msg") 269 | 270 | fun main(args: Array) = runBlocking(CoroutineName("main")) { 271 | log("Started main coroutine") 272 | // run two background value computations 273 | val v1 = async(CoroutineName("v1coroutine")) { 274 | delay(500) 275 | log("Computing v1") 276 | 252 277 | } 278 | val v2 = async(CoroutineName("v2coroutine")) { 279 | delay(1000) 280 | log("Computing v2") 281 | 6 282 | } 283 | log("The answer for v1 / v2 = ${v1.await() / v2.await()}") 284 | } 285 | ``` 286 | 使用 `-Dkotlinx.coroutines.debug` JVM 选项显示结果: 287 | 288 | ``` 289 | [main @main#1] Started main coroutine 290 | [ForkJoinPool.commonPool-worker-1 @v1coroutine#2] Computing v1 291 | [ForkJoinPool.commonPool-worker-2 @v2coroutine#3] Computing v2 292 | [main @main#1] The answer for v1 / v2 = 42 293 | ``` 294 | ### 通过显式的Job取消 295 | 296 | 让我们把我们关于上下文、子协程和任务的知识放下,假设我们的应用有一个生命周期相关的对象,但是这个对象并不是一个协程。比如,我们写一个 Android 应用, 在 Activity 的Context 启动各种协程, 用以执行请求和获取数据的异步操作,执行动画等待。当 Activity 销毁的时候,所有的协程都应该被取消。这可以避免内存泄露。 297 | 298 | 我们可以通过创建一个和 Activity 生命周期关联的 [Job](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/index.html) 实例来管理我们协程的生命周期。一个协程实例使用下面实例的 [Job()](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job.html) 工厂函数生成。方便起见,我们可以写 `launch(coroutineContext, parent = job)` 去显式的指明一个父任务正在被使用的事实,而不是使用 `launch(coroutineContext + job)` 表达式。 299 | 300 | ```kotlin 301 | fun main(args: Array) = runBlocking { 302 | val job = Job() // create a job object to manage our lifecycle 303 | // now launch ten coroutines for a demo, each working for a different time 304 | val coroutines = List(10) { i -> 305 | // they are all children of our job object 306 | launch(coroutineContext, parent = job) { // we use the context of main runBlocking thread, but with our parent job 307 | delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc 308 | println("Coroutine $i is done") 309 | } 310 | } 311 | println("Launched ${coroutines.size} coroutines") 312 | delay(500L) // delay for half a second 313 | println("Cancelling the job!") 314 | job.cancelAndJoin() // cancel all our coroutines and wait for all of them to complete 315 | } 316 | ``` 317 | 318 | 例子输出如下: 319 | ``` 320 | Launched 10 coroutines 321 | Coroutine 0 is done 322 | Coroutine 1 is done 323 | Cancelling the job! 324 | ``` 325 | 326 | 正如你所见,只有最先的三个协程打印了消息, 其他的协程都被 `job.cancelAndJoin()` 单一的调用取消。所以在我们假设的 Android 程序中,我们所需要做的就是在 Activity 创建的时候,创建一个父 job 对象,用它来生成子协程,当 Activity 销毁的时候,取消它。在 Android 的生命周期中,我们不能调用它们的 `join` 函数,因为它是同步的,但是这个加入的能力是在建立后端服务确保有限的资源使用的时候比较有用。 -------------------------------------------------------------------------------- /guide/example/main.md: -------------------------------------------------------------------------------- 1 | ## 用实例入门Kotlinx.couroutines 2 | 3 | 这是一个带有一系列实例的,讲解kotlinx.coroutines核心特性的入门指南 4 | 5 | #### 介绍和设置 6 | 7 | kotlin在标准库中只提供了最低限度的低级 API,这使得使用其他库可以使用 coroutine,和其他具有类似功能的语言不一样的是,async、await不是关键字,也不是标准库的一部分 8 | 9 | kotlinx.coroutines是一个丰富的库,它包含了若干这个指南覆盖的高级协程可用的原语,包括 `launch`、`async` 和其他的一些,您需要添加 `kotlinx-coroutines-core` 的依赖 10 | -------------------------------------------------------------------------------- /guide/imgs/coroutine.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaomaicheng/kotlinx.coroutines-zh/0d325da76a881365dbe19a129dbe95e7eb34b36e/guide/imgs/coroutine.jpg --------------------------------------------------------------------------------