这一篇主要讲解协程的三种启动方式。
一、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是一个接口,它的实现类有很多,常见的有:GlobeScope,LifecycleCoroutineScope,viewModelScope(ViewModel扩展)等,用最简单的GlobeScope(不要用在生产环境)启动一个协程:
fun main() {
GlobalScope.launch {
printMsg("start")
delay(100)
printMsg("end")
}
Thread.sleep(2000L)
}
fun printMsg(msg: String) {
println("${LocalDateTime.now()} ${Thread.currentThread().name}:$msg")
}
//打印
2023-02-10T17:45:11.082989200 DefaultDispatcher-worker-1 @coroutine#1:start
2023-02-10T17:45:11.204989900 DefaultDispatcher-worker-1 @coroutine#1:end
线程名为DefaultDispatcher-worker-1,协程名为@coroutine#1,但会发现代码中有线程sleep的代码,为什么需要sleep呢? 假如去掉会怎么样? 试试看:
fun main() {
GlobalScope.launch {
printMsg("start")
delay(100)
printMsg("end")
}
}
//打印
无
那为什么开启的协程没有线程阻塞(sleep)就不执行了呢? 那是因为通过 launch 创建的协程还没来得及开始执行,整个程序就已经结束了,阻塞线程就是为了让线程不那么快退出。
我们看launch的返回值不是协程内执行的结果,launch 为什么无法将结果返回给调用方呢?我们看上面launch 函数的源代码,你就会发现,这个函数的返回值是一个 Job,它其实代表的是协程的句柄(Handle),它并不能为我们返回协程的执行结果。
二、runBlocking 启动协程
runBlocking 跟我们前面学的 launch 的行为模式不太一样,通过它的名字,我们就可以看出来,它是存在某种阻塞行为的。让我们将前面 launch 的代码直接改为 runBlocking,看看运行结果是否有差异:
fun main() {
runBlocking{
printMsg("start")
delay(2000)
printMsg("end")
}
}
fun printMsg(msg: String) {
println("${LocalDateTime.now()} ${Thread.currentThread().name}:$msg")
}
//打印
2023-02-16 09:12:00.855448200 main @coroutine#1:start
2023-02-16 09:12:02.873297400 main @coroutine#1:end //间隔2秒
即使不加sleep的代码,所有的日志会且按顺序打印,这是因为runBlocking 会阻塞当前线程的执行,。对于这一点,Kotlin 官方也强调了:runBlocking 只推荐用于连接线程与协程,并且,大部分情况下,都只应该用于编写Demo 或是测试代码,所以,请不要在生产环境当中使用 runBlocking。
虽然runBlocking阻塞了线程,但如果在runBlocking中启动其他协程,执行结果也不会完全按顺序执行的,比如:
fun main() {
runBlocking {
printMsg("runBlocking start")
launch {
printMsg("launch start")
delay(2000L)
printMsg("launch end")
}
printMsg("runBlocking end")
}
}
//打印
2023-02-16T09:51:21.818149200 main @coroutine#1:runBlocking start
2023-02-16T09:51:21.823149600 main @coroutine#1:runBlocking end
2023-02-16T09:51:21.826148900 main @coroutine#2:launch start //协程在runBlocking end后面输出
2023-02-16T09:51:23.843212700 main @coroutine#2:launch end
另外,你可以注意到它的第二个参数suspend CoroutineScope.() -> T,这个函数类型是有返回值类型 T 的,而它刚好跟 runBlocking 的返回值类型是一样的。因此,我们可以推测,runBlocking 其实是可以从协程当中返回执行结果的。看下面的示例:
fun main() {
val result = runBlocking<String> {
printMsg("start")
delay(2000)
printMsg("end")
return@runBlocking "Hello World" //添加runBlocking协程作用域的返回值
}
printMsg(result)
}
fun printMsg(msg: String) {
println("${LocalDateTime.now()} ${Thread.currentThread().name}:$msg")
}
//打印
2023-02-16T09:39:33.127854800 main @coroutine#1:start
2023-02-16T09:39:35.141483700 main @coroutine#1:end
2023-02-16T09:39:35.143483600 main:Hello World
所以,从表面上看,runBlocking 是对 launch 的一种补充,但由于它是阻塞式的,因此,runBlocking 并不适用于实际的工作当中。那么,还有什么办法可以让我们拿到协程当中的执行结果吗?答案就是:async。
三、async 启动协程
使用 async{} 创建协程,并且还能通过它返回的句柄拿到协程的执行结果。让我们看个简单的例子:
fun main() {
runBlocking {
printMsg("runBlocking start")
val asyncDeferred: Deferred<String> = async {
printMsg("launch start")
delay(2000L)
printMsg("launch end")
return@async "Hello World"
}
//获取asyncDeferred的结果
val result = asyncDeferred.await() //重点在这里
printMsg("result:$result") //拿到了结果
printMsg("runBlocking end")
}
}
//打印
2023-02-16T09:59:01.415796100 main @coroutine#1:runBlocking start //外部协程执行
2023-02-16T09:59:01.429796100 main @coroutine#2:launch start //内部协程执行
2023-02-16T09:59:03.449260100 main @coroutine#2:launch end //内部协程结束
2023-02-16T09:59:03.453258700 main @coroutine#1:result:Hello World //外部协程输出结果
2023-02-16T09:59:03.453258700 main @coroutine#1:runBlocking end //外部协程结束
为什么等待内部协程执行完才执行外部的协程代码呢? 有二个细节:
① await会让外部协程挂起
② await会等待调用者的协程执行完才会恢复外部协程
await的源码如下:
/**
* Awaits for completion of this value without blocking a thread and resumes when deferred computation is complete,
* returning the resulting value or throwing the corresponding exception if the deferred was cancelled.
*
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* immediately resumes with [CancellationException].
* There is a **prompt cancellation guarantee**. If the job was cancelled while this function was
* suspended, it will not resume successfully. See [suspendCancellableCoroutine] documentation for low-level details.
*
* This function can be used in [select] invocation with [onAwait] clause.
* Use [isCompleted] to check for completion of this deferred value without waiting.
*/
public suspend fun await(): T
注释中有说道,await函数没有阻塞线程,只是等待deferred执行完成就恢复(resumes),而deferred正是async的返回值。