Kotlin 启动协程有 3 种方式:launch、async 和 runBlocking。
在使用协程之前需要引入协程库依赖。
"org.jetbrains.kotlinx:kotlinx-coroutines-core:$versions.coroutines"
在运行示例前,配置协程调试 VM 参数:
Edit config -> VM options
-Dkotlinx.coroutines.debug
launch
launch用来启动协程,但是不需要获取运行后的返回结果。它类似于”射箭“场景,将箭发射出去,但是不需要箭返回。使用者不关心协程的返回值。
launch 使用方法
使用 launch 时需要一个协程作用域,这里先使用 GlobalScope。
fun main() {
GlobalScope.launch {
println("Coroutine started: ${Thread.currentThread().name}")
delay(1000L)
println("Hello World!")
}
println("After launch:${Thread.currentThread().name}")
Thread.sleep(2000L)
}
输出结果
After launch:main
Coroutine started: DefaultDispatcher-worker-1 @coroutine#1
Hello World!
Process finished with exit code 0
可以看出先执行 main 的代码,再执行 launch 中的代码。launch 中使用了 delay 方法延迟 1 秒,然后打印语句。主线程休眠 2 秒后程序运行结束。
因为配置了 -Dkotlinx.coroutines.debug 参数,launch 打印线程名称时也打印了协程名称。
Coroutine started: DefaultDispatcher-worker-1 @coroutine#1
协程 coroutine#1 运行在 DefaultDispatcher-worker-1 线程。
如果去掉最后一句 sleep,程序没有输出结果。
fun main() {
GlobalScope.launch {
println("Coroutine started: ${Thread.currentThread().name}")
delay(1000L)
println("Hello World!")
}
println("After launch:${Thread.currentThread().name}")
// Thread.sleep(2000L)
}
输出结果
After launch:main
Process finished with exit code 0
可以看出 launch 启动的协程没有运行。这是因为此时 launch 的方法执行在守护线程。守护线程的特点是如果所有非守护线程运行结束,守护线程会自动停止。在 main 线程运行结束后,launch 停止,里面的代码没有执行。
如果我们将 launch 中的 delay 替换成 sleep,编译器会提示:Inappropriate blocking method call
fun main() {
GlobalScope.launch {
println("Coroutine started: ${Thread.currentThread().name}")
// Inappropriate blocking method call
Thread.sleep(1000L)
println("Hello World!")
}
println("After launch:${Thread.currentThread().name}")
Thread.sleep(2000L)
}
因为协程中应该运行非阻塞的函数,而 delay 就是一个非阻塞的挂起函数。挂起函数的特点就是”挂起-恢复“,是非阻塞的。
delay 函数定义,具有 suspend 关键词。
public suspend fun delay(timeMillis: Long) {
if (timeMillis <= 0) return // don't delay
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
launch 介绍
launch 方法的源码如下:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
launch 是 CoroutineScope 的扩展方法,只能在 CoroutineScope 的内部运行。因此在使用时需要一个协程作用域。
GlobalScope.launch 表示协程运行在全局作用域 GlobalScope。
launch 方法有 3 个参数:context,start,block。分别是 CoroutineContext、CoroutineStart 和 suspend CoroutineScope.() -> Unit 类型。
CoroutineContext 是协程上下文,通常用来指定协程的执行线程。
CoroutineStart 是协程启动方式,默认是 DEFAULT 方式。DEFAULT 表示协程立即执行。还有一个 LAZY 方式,表示协程懒加载执行。
suspend CoroutineScope.() -> Unit 表示协程需要执行的匿名函数类型。这个函数是挂起函数,用 suspend 关键字表示。它是 CoroutineScope 的扩展函数,用 CoroutineScope.() -> Unit 表示。() -> Unit 表示函数入参为空,返回值也为空。
runBlocking
runBlocking 也能用来启动协程,但是它是阻塞的。
runBlocking 使用
fun main() {
runBlocking { // 1
println("Coroutine started!") // 2
delay(1000L) // 3
println("Hello World!") // 4
}
println("After launch!") // 5
Thread.sleep(2000L) // 6
println("Process end!") // 7
}
输出结果:
Coroutine started!
Hello World!
After launch!
Process end!
可以看出 runBlocking 是顺序执行的。main 里面的代码会等待 runBlocking 中的代码执行完毕。
runBlocking 介绍
runBlocking 是一个顶层函数,定义在 Builders.kt。
runBlocking 在内部构造了 newContext 协程上下文,然后调用 start 方法启动。最后使用 joinBlocking 返回。
@Throws(InterruptedException::class)
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
val currentThread = Thread.currentThread()
val contextInterceptor = context[ContinuationInterceptor]
val eventLoop: EventLoop?
val newContext: CoroutineContext
if (contextInterceptor == null) {
// create or use private event loop if no dispatcher is specified
eventLoop = ThreadLocalEventLoop.eventLoop
newContext = GlobalScope.newCoroutineContext(context + eventLoop)
} else {
// See if context's interceptor is an event loop that we shall use (to support TestContext)
// or take an existing thread-local event loop if present to avoid blocking it (but don't create one)
eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
?: ThreadLocalEventLoop.currentOrNull()
newContext = GlobalScope.newCoroutineContext(context)
}
val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
return coroutine.joinBlocking()
}
runBlocking 的最后一个参数是block: suspend CoroutineScope.() -> T),同时也返回 T。因此它可以返回协程执行结果。
runBlocking 是对 launch 的一种补充,但由于它是阻塞式的,因此,runBlocking 并不适用于实际的工作当中。
async
async 使用
fun main() = runBlocking {
println("In runBlocking:${Thread.currentThread().name}")
val deferred: Deferred<String> = async {
println("In async:${Thread.currentThread().name}")
delay(1000L) // 模拟耗时操作
return@async "Task completed!"
}
println("After async:${Thread.currentThread().name}")
val result = deferred.await()
println("Result is: $result")
}
输出结果
In runBlocking:main @coroutine#1
After async:main @coroutine#1 // 注意,它比“In async”先输出
In async:main @coroutine#2
Result is: Task completed!
可以看出 async 是非阻塞的,同时用 await 返回执行结果。
async 介绍
async 的行为类似钓鱼模式,抛出鱼竿,然后拉回鱼竿。
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
async 返回 Deferred 类型,用来获取结果。