Appearance
协程是可暂停计算的一个实例。它在概念上类似于线程,因为它需要运行一段代码,该代码块与其余代码同时运行。但是,协程并不绑定到任何特定线程。它可以在一个线程中暂停执行,并在另一个线程中恢复执行。
协程可以被认为是轻量级线程,但是存在许多重要的区别,使得它们在实际使用与线程有很大不同。
运行以下代码以获取第一个工作协程:
kotlin
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking { // this: CoroutineScope
launch { // 启用一个新的协程并继续
delay(1000L) // 非阻塞延迟一秒
println("World!") // 延迟后打印
}
println("Hello") // 主协程继续,前一个协程被延迟
}输出
Hello
World!launch是一个协程构建器。它与其余代码同时启动一个新的协程,其余代码继续独立工作。这就是为什么Hello 被首先打印出来。
delay是一个特殊的暂停函数。它会将协程暂停一段特定的时间。暂停协程不会阻塞底层线程,但允许其他协程运行并使用底层线程执行其代码。
runBlocking也是一个协程构建器,它将常规的非协程世界fun main()与花括号内的协程代码连接起来runBlocking { ... }。在 IDE 中,花括号this: CoroutineScope后面的提示会突出显示runBlocking。
runBlocking意味着运行它的线程(在本例中是主线程)在调用期间被阻塞runBlocking { ... },直到里面的所有协程完成执行。在实际代码中很少看到这么使用,因为线程是昂贵的资源,阻塞它们是低效的。
结构化并发
协程遵循结构化并发原则,这意味着只能在特定的CoroutineScope中启动新的协程,该 CoroutineScope 限定了协程的生命周期。上面的示例表明runBlocking建立了相应的范围,这就是为什么上一个示例在延迟一秒钟后等待直到World!打印出来才退出。
在实际应用中,您将启动大量协程。结构化并发可确保它们不会丢失且不会泄漏。外部作用域在其所有子协程完成之前无法完成。结构化并发还可确保正确报告代码中的任何错误并且绝不会丢失。
提取函数重构
kotlin
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking { // this: CoroutineScope
launch { doWorld() }
println("Hello")
}
suspend fun doWorld() {
delay(1000L)
println("World!")
}作用域构建器
除了不同构建器提供的协程作用域外,还可以使用coroutineScope构建器声明自己的作用域。它会创建一个协程作用域,直到所有启动的子进程完成才会完成。
runBlocking和coroutineScope构建器可能看起来相似,因为它们都等待其主体及其所有子级完成。主要区别在于runBlocking方法会阻止当前线程等待,而coroutineScope只是暂停,释放底层线程以供其他用途。由于这种差异,runBlocking是一个常规函数,而coroutineScope是一个暂停函数。
您可以从任何暂停函数中使用。例如,您可以将和coroutineScope的并发打印移到函数中:Hello World suspend fun doWorld()
kotlin
fun main() = runBlocking {
doWorld()
}
suspend fun doWorld() = coroutineScope { // this: CoroutineScope
launch {
delay(1000L)
println("World!")
}
println("Hello")
}范围构建器和并发
协程作用域构建器可在任何挂起函数内使用,以执行多个并发操作。让我们在doWorld挂起函数内启动两个并发协程:
kotlin
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
// Sequentially executes doWorld followed by "Done"
fun main() = runBlocking {
doWorld()
println("Done")
}
// Concurrently executes both sections
suspend fun doWorld() = coroutineScope { // this: CoroutineScope
launch {
delay(2000L)
println("World 2")
}
launch {
delay(1000L)
println("World 1")
}
println("Hello")
}输出
Hello
World 1
World 2
Done明确的工作
启动协程构建器返回一个Job对象,该对象是已启动协程的句柄,可用于显式等待其完成。例如,您可以等待子协程完成,然后打印“Done”字符串:
kotlin
package org.example.coroutines
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val job = launch { // launch a new coroutine and keep a reference to its Job
delay(1000L)
println("World!")
}
println("Hello")
job.join() // wait until child coroutine completes
println("Done")
}输出
Hello
World!
Done协程是轻量级的
协程比 JVM 线程占用的资源更少。使用线程时耗尽 JVM 可用内存的代码可以使用协程来表达,而不会达到资源限制。例如,以下代码启动 50,000 个不同的协程,每个协程等待 5 秒,然后打印一个句点(“。”),同时消耗很少的内存:
kotlin
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
fun main() = runBlocking {
repeat(50_000) { // launch a lot of coroutines
launch {
delay(5000L)
print(".")
}
}
}