Coroutines on Android (part II): getting started
这是Andoid 协程系列文章中的第二篇。这篇将关注协程是怎样开始运行的。
1. 跟踪协程
在第一篇文章中,讲述了协程擅长于解决的是哪类问题。回顾一下,协程擅长于解决的是如下两类常见的问题:
- 会阻塞主线程的长时间耗时任务
- 主线程安全。任何suspend 函数都可以在主线程被安全的调用。
为了解决上述两个问题,协程通过在普通函数上增加suspend 和 resume。当特定线程上的所有协程都被挂起的时候,该线程可以自由的做其他工作
然而,协程它们自己并不能跟踪自己正在做的事。大量的协程(甚至说成千上万)在同一时间都suspend也是没有问题的。尽管协程是轻量级、它们所要做的事却是重量级的,比如读取文件,网络请求等。
手动使用代码来追踪1000个协程是相当困难的。你可以尝试追踪,并且手动来确保它们已经完成或是被取消,但是这样的代码可能很冗长或是容易出错。如果代码不够完善,有可能对协程失去追踪,这可以称之为 work leak
work leak和内存 泄漏很像,但也更糟糕。如果一个协程缺失追踪和管理,除了会涉及内存,也会包括CPU/磁盘 等资源的占用,甚至还发起网络请求。
协程泄漏会浪费内存,cpu,磁盘,甚至会继续发起并不需要的网络请求
为了解决协程泄漏,kotlin引入了结构化的并发,结构化并发是语言特性和最佳实践的组合,如果遵循这些特性,可以帮助跟踪协程运行中的所有工作。
在Android 上,我们可以使用结构化并发来做以下三件事
- 当不再需要的时候,协程是可以取消的
- 在运行的时候,可以追踪到是怎样工作的
- 协程失败的时候,可以发出错误信号
让我们再深入探讨以上三个方面,看看结构化并发是怎样保证我们不会缺失对协程的追踪从而避免work leak。
2. 使用scopes 取消协程
在Kotlin中,协程必须运行在CoroutineScope里。CoroutineScope会负责跟踪协程,甚至是suspend的协程。不像我们在其他文章中提到的Dispatchers,CoroutineScope并不执行协程,它仅仅是保证你不会跟掉协程。
为了确保所有的协程能被追踪到,Kotlin 是不会允许在没有CoroutineScope的情况里开启一个新协程的。你可以把CoroutineScope 想象成一个轻量级但是又非常强大的ExecutorService。CoroutineScope赋予了启动协程的能力。
CoroutineScope 既能跟踪所有的协程,也可以取消在它里面启动的协程。
1. 开启协程的方法
要记住,并不是在任何地方都可以调用suspend 函数。suspend 和 resume 机制要求你从普通函数上切换到协程中。
有两种方法启动协程,它们有不同的使用用途
- 使用launch 将会构建一个不带返回结果给调用者的协程
- 使用async 将会构建一个当调用await会有返回结果给调用者的协程
在大多数情况下,在普通函数上启动协程话的可以使用launch。因为普通函数上没法调用await ,因此也没必要使用async。稍后会讨论在什么情况下有必要使用async
lancun又是在scope中使用的,因此,如下方式启动协程
lifecycleScope.launch{
// This block starts a new coroutine
// "in" the scope.
//
// It can call suspend functions
request()
}
GlobalScope.launch {
request()
}
你可以把launch想象成一座桥,把普通函数切换到协程的环境中。在launch 里,你可以代用suspend 函数,并且是主线程安全的。
注意:launch和async 之间的一个巨大差异就是如何处理异常。async希望你最终调用await 来获取结果或是异常,这样它就不用默认的抛出异常了。这也意味着,如果你使用async来开启一个协程,有异常也会悄无声息的丢弃异常。
既然launch 和 async只能在CoroutineScope上使用,所以你创建的任何协程都将会被这个scope追踪到。Kotlin 不会让你创建一个追踪不到的协程,这会导致work leaks。