type
status
date
slug
summary
tags
category
icon
password
概念
协程运行在协程上下文中(CoroutineContext)。
协程上下文包含一个协程调度器(CoroutineDispatcher),它可以将协程限制在一个特定的线程中执行,或者将协程分配到一个线程池中,或者让它不受限制的运行。
CoroutineContext使用以下元素(Element)定义协程的行为:
- Job: 控制协程的生命周期
- CoroutineDispatcher: 将工作(协程)分派到适当的线程
- CoroutineName: 协程的名字,可用于调试
- CoroutineExceptionHandler: 处理未捕获的异常
CoroutineScope
会跟踪它使用launch或者async创建的所有协程,可以随时调用scope.cancel()取消正在运行的协程.但是,已取消的scope是不能再创建协程的,这一点需要注意.与调度程序不同,CoroutineScope并不运行协程.
Job
是协程的句柄,使用launch或者async创建的每个协程都会返回一个job对象,这个对象唯一标识协程并管理协程的生命周期,当然也可以将Job传递给CoroutineScope进一步管理其生命周期.执行job.cancel()不影响CoroutineScope.协程可以在一个线程上挂起,并在其他线程上恢复.
Dispatcher
主要使用3种Dispatcher:
- Dispatchers.Main
运行在主线程
- Dispatchers.IO
适合主线程之外的磁盘/网络IO
- Dispatchers.Default
适合主线程之外占有大量CPU资源的工作
可以使用
withContext(Dispatcher)
来指定:suspend fun fetchDocs() { // Dispatchers.Main val result = get("developer.android.com") // Dispatchers.Main show(result) // Dispatchers.Main } suspend fun get(url: String) = // Dispatchers.Main withContext(Dispatchers.IO) { // Dispatchers.IO (main-safety block) /* perform network IO here */ // Dispatchers.IO (main-safety block) } // Dispatchers.Main }
withContext
在性能上有优势,可以避免频繁的线程切换.启动协程
两种方式:
- launch
不返回结果,从常规函数启动协程一般用launch,因为常规函数无法调用await
- async
返回结果 (使用await),在另一个协程中或者在挂起函数中且在执行并行分解时才使用async
两者处理异常的方式不同,async持有异常,并作为结果在await中返回.因此,如果使用await从常规函数启动,则会丢弃异常信息.
在普通方法中启动协程一般使用下面两种方法:
- launch() (不会阻塞当前线程)
- runBlocking {} (一般用于测试,将后台认为进行同步处理,防止过早退出)
并行分解
由suspend函数启动的所有协程都必须在函数返回结果之前停止,因此需要保证这些协程在返回结果之前完成.
suspend fun fetchTwoDocs() = coroutineScope { val deferredOne = async { fetchDoc(1) } val deferredTwo = async { fetchDoc(2) } deferredOne.await() deferredTwo.await() } suspend fun fetchTwoDocs() = // called on any Dispatcher (any thread, possibly Main) coroutineScope { val deferreds = listOf( // fetch two docs at the same time async { fetchDoc(1) }, // async returns a result for the first doc async { fetchDoc(2) } // async returns a result for the second doc ) deferreds.awaitAll() // use awaitAll to wait for both network requests }
自定义CoroutineScope
class ExampleClass { // Job and Dispatcher are combined into a CoroutineContext which // will be discussed shortly val scope = CoroutineScope(Job() + Dispatchers.Main) fun exampleMethod() { // Starts a new coroutine within the scope scope.launch { // New coroutine that can call suspend functions fetchDocs() } } fun cleanUp() { // Cancel the scope to cancel ongoing coroutines work scope.cancel() } }
GlobalScope
public object GlobalScope : CoroutineScope { /** * Returns [EmptyCoroutineContext]. */ override val coroutineContext: CoroutineContext get() = EmptyCoroutineContext }
使用GlobalScope.launch 的时候,会创建一个顶层协程,如果忘记保持对新启动的协程的引用,它还会继续运行,如果挂起了(比如delay了太久),必须手动保持对所有已经启动协程的引用.并调用join()方法.
在GlobalScope中启动的活动协程并不会使进程保活,它们就像守护线程.
阻塞与非阻塞
先看一段代码:
import kotlinx.coroutines.* fun main() = runBlocking<Unit> { // 开始执行主协程 GlobalScope.launch { // 在后台启动一个新的协程并继续 delay(1000L) println("World!") } println("Hello,") // 主协程在这里会立即执行 }
你觉得会输出什么结果?
结果是:
Hello,
对,GlobalScope.launch中的代码快并没有执行到打印“World”.
这个时候可以再主协程中添加一个delay,但是方法未免太过死板,可以使用JOb控制:
val job = GlobalScope.launch { // 启动一个新协程并保持对这个作业的引用 delay(1000L) println("World!") } println("Hello,") job.join() // 等待直到子协程执行结束
但是,如果有很多协程,每个都去获取job并join的话也太容易出错了,因此:
import kotlinx.coroutines.* fun main() = runBlocking { // this: CoroutineScope launch { // 在 runBlocking 作用域中启动一个新协程 delay(1000L) println("World!") } println("Hello,") }
直接在对应的coroutineScope中启动协程,而不是使用GlobalScope.
调用顺序问题
- 顺序调用
val time = measureTimeMillis { val one = doSomethingUsefulOne() val two = doSomethingUsefulTwo() println("The answer is ${one + two}") } println("Completed in $time ms")
- async并发
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 concurrentSum(): Int = coroutineScope { val one = async { doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } one.await() + two.await() }
- Lazy 的 async并发
val time = measureTimeMillis { val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() } val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() } // 执行一些计算 one.start() // 启动第一个 two.start() // 启动第二个 println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms")
只有结果通过await获取的时候协程才会启动,或者在Job的start函数调用的时候.
参考文档
- 作者:姜康
- 链接:https://jiangkang.tech/article/2191eded-3037-4524-9f7a-10dc1476969d
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章