Kotlin协程的三种启动方式

launch 启动协程

GlobalScope.launch{},它是一个高阶函数,它的作用就是启动一个协程。GlobalScope 是 Kotlin 官方为我们提供的“协程作用域”,delay(),它的作用就是字面上的意思,“延迟”。以上代码中,我们是延迟了 1 秒。从 delay() 的函数签名这里可以发现,它的定义跟普通的函数不太一样,它多了一个“suspend”关键字,这代表了它是一个挂起函数。而这也就意味着,delay 将会拥有“挂起和恢复”的能力。协程代码特殊的行为模式,那就是:它的代码不是按照顺序执行的。launch 为什么无法将结果返回给调用方呢?如果你去看 launch 函数的源代码,你就会发现,这个函数的返回值是一个 Job,它其实代表的是协程的句柄(Handle),它并不能为我们返回协程的执行结果。

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
)
: Job { ... }

首先是 CoroutineScope.launch(),代表了 launch 其实是一个扩展函数,而它的“扩展接收者类型”是 CoroutineScope。这就意味着,我们的 launch() 会等价于 CoroutineScope 的成员方法。而如果我们要调用 launch() 来启动协程,就必须要先拿到 CoroutineScope 的对象。前面的案例,我们使用的 GlobalScope,其实就是 Kotlin 官方为我们提供的一个 CoroutineScope 对象,方便我们开发者直接启动协程。接着是第一个参数:CoroutineContext,它代表了我们协程的上下文,它的默认值是 EmptyCoroutineContext,如果我们不传这个参数,默认就会使用 EmptyCoroutineContext。一般来说,我们也可以传入 Kotlin 官方为我们提供的 Dispatchers,来指定协程运行的线程池。协程上下文,是协程当中非常关键的元素. 然后是第二个参数:CoroutineStart,它代表了协程的启动模式。如果我们不传这个参数,它会默认使用 CoroutineStart.DEFAULT。CoroutineStart 其实是一个枚举类,一共有:DEFAULT、LAZY、ATOMIC、UNDISPATCHED。我们最常使用的就是 DEFAULT、LAZY,它们分别代表:立即执行、懒加载执行。最后一个参数,是一个函数类型的 block,它的类型是“suspend CoroutineScope.() -> Unit”。对于“suspend CoroutineScope.(Int) -> Double”这个函数类型,你应该也能轻松解释了。首先,它应该是一个“挂起函数”,同时,它还应该是 CoroutineScope 类的成员方法或是扩展方法,并且,它的参数类型必须是 Int,返回值类型必须是 Double。

runBlocking 启动协程

runBlocking 跟我们前面学的 launch 的行为模式不太一样,通过它的名字,我们就可以看出来,它是存在某种阻塞行为的。使用 runBlocking 启动的协程会阻塞当前线程的执行,这样一来,所有的代码就变成了顺序执行。Kotlin 官方也强调了:runBlocking 只推荐用于连接线程与协程,并且,大部分情况下,都只应该用于编写 Demo 或是测试代码。所以,请不要在生产环境当中使用 runBlocking。

public actual fun <T> runBlocking(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T)
: T {
...
}

runBlocking 就是一个普通的顶层函数,它并不是 CoroutineScope 的扩展函数,因此,我们调用它的时候,不需要 CoroutineScope 的对象。前面我们提到过,GlobalScope 是不建议使用的. runBlocking 其实是可以从协程当中返回执行结果的

async 启动协程

在 Kotlin 当中,我们可以使用 async{} 创建协程,并且还能通过它返回的句柄拿到协程的执行结果。请注意 async{}的返回值,它是一个 Deferred 对象,我们通过调用它的 await() 方法,就可以拿到协程的执行结果。我们再来看看 async 的函数签名,顺便对比一下它跟 launch 之间的差异:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit // 不同点1
)
: Job {} // 不同点2

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T // 不同点1
)
: Deferred<T> {} // 不同点2

从上面的代码中,我们可以发现 launch 和 async 的两个不同点,一个是 block 的函数类型,前者的返回值类型是 Unit,后者则是泛型 T;另外一个不同点在返回值上,前者返回值类型是 Job,后者返回值类型是 Deferred。而 async 可以返回协程执行结果的原因也在于此。

总结

我们还学到了三种启动协程的方式,分别是 launch、runBlocking、async。

  • launch,是典型的“Fire-and-forget”场景,它不会阻塞当前程序的执行流程,使用这种方式的时候,我们无法直接获取协程的执行结果。它有点像是生活中的射箭。
  • runBlocking,我们可以获取协程的执行结果,但这种方式会阻塞代码的执行流程,因为它一般用于测试用途,生产环境当中是不推荐使用的。
  • async,则是很多编程语言当中普遍存在的协程模式。它像是结合了 launch 和 runBlocking 两者的优点。它既不会阻塞当前的执行流程,还可以直接获取协程的执行结果。它有点像是生活中的钓鱼。


— End —

Kotlin协程的三种启动方式

原文始发于微信公众号(君伟说):Kotlin协程的三种启动方式

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/115156.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!