赞
踩
协程从Kotlin1.3开始引入,本质上协程就是轻量级的线程。协程的基本功能点有:
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
println("World!") // 在延迟后打印输出
}
println("Hello,") // 协程已在等待时主线程还在继续
Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}
可以将 GlobalScope.launch { …… } 替换为 thread { …… },并将 delay(……) 替换为 Thread.sleep(……) 达到同样目的。但是如果你只更换其中一个的话就会有bug了,因为delay是一个挂起函数,不会造成线程阻塞,且只能在协程中使用。
上述代码中牵扯到以下几个概念:
第一个示例在同一段代码中混用了 非阻塞的 delay(……) 与 阻塞的 Thread.sleep(……)。 这容易让我们记混哪个是阻塞的、哪个是非阻塞的。 让我们显式使用 runBlocking 协程构建器来阻塞:
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(1000L)
println("World!")
}
println("Hello,") // 主线程中的代码会立即执行
runBlocking { // 但是这个表达式阻塞了主线程
delay(2000L) // ……我们延迟 2 秒来保证 JVM 的存活
}
}
与线程中的join类似,当调用join方法后会等待该协程的任务结束。
val job = GlobalScope.launch { // 启动一个新协程并保持对这个作业的引用
delay(1000L)
println("World!")
}
println("Hello,")
job.join() // 等待直到子协程执行结束
当我们使用 GlobalScope.launch 时,我们会创建一个顶层协程。虽然它很轻量,但它运行时仍会消耗一些内存资源。如果我们忘记保持对新启动的协程的引用,它还会继续运行。所以我们可以通过使用runBlocking协程构造器将main函数构造为协程,这样就能在main结束时保证所有main中的协程结束。
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
launch { // 在 runBlocking 作用域中启动一个新协程
delay(1000L)
println("World!")
}
println("Hello,")
}
除了由不同的构建器提供协程作用域之外,还可以使用 coroutineScope 构建器声明自己的作用域。它会创建一个协程作用域并且在所有已启动子协程执行完毕之前不会结束。
runBlocking 与 coroutineScope 可能看起来很类似,因为它们都会等待其协程体以及所有子协程结束。 主要区别在于,runBlocking 方法会阻塞当前线程来等待, 而 coroutineScope 只是挂起,会释放底层线程用于其他用途。 由于存在这点差异,runBlocking 是常规函数,而 coroutineScope 是挂起函数。
import kotlinx.coroutines.* fun main() = runBlocking { // this: CoroutineScope launch { delay(200L) println("Task from runBlocking") } coroutineScope { // 创建一个协程作用域 launch { delay(500L) println("Task from nested launch") } delay(100L) println("Task from coroutine scope") // 这一行会在内嵌 launch 之前输出 } println("Coroutine scope is over") // 这一行在内嵌 launch 执行完毕后才输出 }
我们来将 launch { …… } 内部的代码块提取到独立的函数中。当你对这段代码执行“提取函数”重构时,你会得到一个带有 suspend 修饰符的新函数。 这是你的第一个挂起函数。在协程内部可以像普通函数一样使用挂起函数, 不过其额外特性是,同样可以使用其他挂起函数(如本例中的 delay)来挂起协程的执行。
import kotlinx.coroutines.*
fun main() = runBlocking {
launch { doWorld() }
println("Hello,")
}
// 这是你的第一个挂起函数
suspend fun doWorld() {
delay(1000L)
println("World!")
}
在协程中可以精确控制,使用的操作就是协程的取消:
val job = launch {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // 延迟一段时间
println("main: I'm tired of waiting!")
job.cancel() // 取消该作业
job.join() // 等待作业执行结束
println("main: Now I can quit.")
这里必须配合join使用的目的是,协程的取消并不是一定的,有可能取消了之后协程中还会有任务执行。我们也可以使用cancel和join的组合函数cancelAndJoin
在实践中绝大多数取消一个协程的理由是它有可能超时。 当你手动追踪一个相关 Job 的引用并启动了一个单独的协程在延迟后取消追踪,这里已经准备好使用 withTimeout 函数来做这件事。 来看看示例代码:
withTimeout(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
当我们调用两个挂起函数时,代码会根据默认顺序运行:
import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlin.system.measureTimeMillis suspend fun doSomethingUsefulOne(): Int { delay(1000L) // 假设我们在这里做了一些有用的事 return 13 } suspend fun doSomethingUsefulTwo(): Int { delay(1000L) // 假设我们在这里也做了一些有用的事 return 29 } fun main()= runBlocking { val time = measureTimeMillis { val one = doSomethingUsefulOne() val two = doSomethingUsefulTwo() println("The answer is ${one + two}") } println("Completed in $time ms") }
输出结果:
此时为了提高效率,我们可以使用async进行并发编程。async实际上启动了一个单独的协程,与launch类似,但是launch返回一个Job且无附带任何结果值,async返回一个Deferred(一个轻量级的非阻塞future),可以使用.await()在一个延期时间取得最终的值。且Deferred也是一个Job,需要时也可对其进行取消操作。
import kotlinx.coroutines.* import kotlin.system.* fun main() = runBlocking<Unit> { val time = measureTimeMillis { val one = async { doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms") } suspend fun doSomethingUsefulOne(): Int { delay(1000L) // 假设我们在这里做了些有用的事 return 13 } suspend fun doSomethingUsefulTwo(): Int { delay(1000L) // 假设我们在这里也做了些有用的事 return 29 }
可选的,可以将 async 的 start 参数设置为 CoroutineStart.lazy 使其变为懒加载模式。在这种模式下,只有在主动调用 Deferred 的 await() 或者 start() 方法时才会启动协程。运行以下示例:
import kotlinx.coroutines.* import kotlin.system.* fun main() = runBlocking<Unit> { //sampleStart val time = measureTimeMillis { val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() } val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() } // some computation one.start() // start the first one two.start() // start the second one println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms") //sampleEnd } suspend fun doSomethingUsefulOne(): Int { delay(1000L) // pretend we are doing something useful here return 13 } suspend fun doSomethingUsefulTwo(): Int { delay(1000L) // pretend we are doing something useful here, too return 29 }
以上定义了两个协程,但没有像前面的例子那样直接执行,而是将控制权交给了开发者,由开发者通过调用 start() 函数来确切地开始执行。首先启动了协程 one,然后启动了协程 two,然后再等待协程运行结束
注意,如果只是在 println 中调用了 await() 而不首先调用 start() ,这将形成顺序行为,因为 await() 会启动协程并等待其完成,这不是 lazy 模式的预期结果。async(start=CoroutineStart.LAZY) 的用例是标准标准库中的 lazy 函数的替代品,用于在值的计算涉及挂起函数的情况下。
让我们使用使用 async 的并发这一小节的例子并且提取出一个函数并发的调用 doSomethingUsefulOne 与 doSomethingUsefulTwo 并且返回它们两个的结果之和。 由于 async 被定义为了 CoroutineScope 上的扩展,我们需要将它写在作用域内,并且这是 coroutineScope 函数所提供的:
suspend fun concurrentSum(): Int = coroutineScope {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
one.await() + two.await()
}
这种情况下,如果在 concurrentSum 函数内部发生了错误,并且它抛出了一个异常, 所有在作用域中启动的协程都会被取消。
import kotlinx.coroutines.* import kotlin.system.* fun main() = runBlocking<Unit> { val time = measureTimeMillis { println("The answer is ${concurrentSum()}") } println("Completed in $time ms") } suspend fun concurrentSum(): Int = coroutineScope { val one = async { doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } one.await() + two.await() } suspend fun doSomethingUsefulOne(): Int { delay(1000L) // 假设我们在这里做了些有用的事 return 13 } suspend fun doSomethingUsefulTwo(): Int { delay(1000L) // 假设我们在这里也做了些有用的事 return 29 }
请注意,如果其中一个子协程(即 two)失败,第一个 async 以及等待中的父协程都会被取消:
import kotlinx.coroutines.* fun main() = runBlocking<Unit> { try { failedConcurrentSum() } catch(e: ArithmeticException) { println("Computation failed with ArithmeticException") } } suspend fun failedConcurrentSum(): Int = coroutineScope { val one = async<Int> { try { delay(Long.MAX_VALUE) // 模拟一个长时间的运算 42 } finally { println("First child was cancelled") } } val two = async<Int> { println("Second child throws an exception") throw ArithmeticException() } one.await() + two.await() }
上一篇:Kotlin新手教程八(泛型)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。