Kotlin CoroutineContext 协程上下文

126 阅读2分钟

Kotlin CoroutineContext 协程上下文

  • CoroutineContext 是一个接口,有个 CoroutineContext.Element 接口继承了 CoroutineContext 接口,定义了一系列协程执行的相关元素
  • CoroutineContext 接口的设计和 Map 很相识,也是根据 Key 取值的,配合操作符重载(plus 加号、get 中括号)可以写出比较灵活的代码,如果子协程创建时指定了 CoroutineContext,则会合并,相同 Key 的值会被覆盖,总得来说集合包含了用户定义的一些各种不同 Element 元素对象,包括管理了协程的生命周期,线程调度,异常处理等
    • CoroutineName 代表协程的名称,通常可用于调试或者日志记录的时候的识别
    • Job 代表一个协程任务,算是已启动协程的句柄,可以用来跟踪协程的状态以及对它的控制,即用于管理协程的生命周期
    • CoroutineDispatcher(Dispatchers)用于协程处理线程调度,将任务分派到当的线程
    • CoroutineExceptionHandler 用于协程的异常处理,处理未捕获的异常
    • ContinuationInterceptor 的作用是拦截协程的,CoroutineDispatcher 也实现了 ContinuationInterceptor 接口
  • EmptyCoroutineContext 是一个 object 类实现了 CoroutineContext 接口,一般作为默认的协程上下文来使用,比如 launch 或者 async 的第一个 CoroutineContext 参数默认就是 EmptyCoroutineContext
  • CombinedContext 组合的上下文实现了 CoroutineContext 接口,由于 CoroutineContext 也重载了 plus 操作符,而 CombinedContext 就是 + 号组合得到的
  • 也可以通过 minusKey 函数移除元素

CoroutineName 协程名称

  • CoroutineName 是一个 data 数据类继承了 AbstractCoroutineContextElement 类(实现了 CoroutineContext.Element 接口)
  • 是用来给协程命名的,支持传入 name 参数,重写的 toString 方法可输出 "CoroutineName($name)" 格式字符串

Job 任务

  • Job 是一个接口,继承了 CoroutineContext.Element 接口
  • 拥有 isActive、isCompleted 和 isCancelled 等属性
  • 拥有 start、 cancel 和 join 等方法,比如可以调用 job.cancel() 取消协程
  • Deferred 接口继承了 Job

CoroutineDispatcher(Dispatchers)协程调度器

  • Dispatchers 是一个 object 类,调度器确定了协程在哪个线程或哪些线程上执行,它可以将协程限制在一个特定的线程中执行,或将它分派到一个线程池,也或者让它不受限地运行
  • 其中抽象类 CoroutineDispatcher 继承了 AbstractCoroutineContextElement 类(实现了 CoroutineContext.Element 接口)和 ContinuationInterceptor 接口

Dispatchers 里的 CoroutineDispatcher 类型,可以看到它叫 Dispatchers 而不是 Threads,比如 Threads.Main,Threads.IO 等等

  • 1 Dispatchers.Default 默认,对应 DefaultScheduler,为 JVM 共享线程池,$schedulerName-worker-${index},默认前缀是 DEFAULT_SCHEDULER_NAME = "DefaultDispatcher",比如 DefaultDispatcher-worker-1,比如用于图片裁剪、二维码计算和 Json 解析操作等
  • 2 Dispatchers.Main 对应 MainCoroutineDispatcher,Android 主线程
  • 3 Dispatchers.IO 对应 DefaultIoScheduler,IO 线程池,默认为 64 个线程,比如用于网络请求,文件读写,数据库操作等
  • 4 Dispatchers.Unconfined 对应 kotlinx.coroutines.Unconfined,不限制,当前 CoroutineScope 的线程策略
  • ExecutorService 的 asCoroutineDispatcher 扩展方法可以把线程池作为协程调度器,比如 Executors.newSingleThreadExecutor().asCoroutineDispatcher()
  • Handler 的 asCoroutineDispatcher 扩展方法可以把 Handler 所在线程作为协程调度器,比如 Handler(Looper.getMainLooper()).asCoroutineDispatcher()

CoroutineExceptionHandler 协程异常处理器

  • CoroutineExceptionHandler 是一个接口,继承了 CoroutineContext.Element 接口

  • 统一给父协程定义一个 CoroutineExceptionHandler,如果一个子协程异常了,其他子协程也不需要继续的话就采用 coroutineScope 方法,默认的 Job 就是这种表现,如果一个子协程异常了,其他子协程还需要继续的话就采用 supervisorScope 方法,或者创建协程作用域的时候传入带 SupervisorJob() 函数返回的特殊 Job 的协程上下文

  • 子协程如果想单独处理异常就单独给子协程定义一个 CoroutineExceptionHandler

  • 传入 exceptionHandler 方式和 supervisorScope

 val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        //CoroutineExceptionHandler#handleException 方法回调
        Log.d("exceptionHandler", "${coroutineContext[CoroutineName].toString()} 处理异常 :$throwable")
    }
    GlobalScope.launch(exceptionHandler) {
        supervisorScope {
            launch(CoroutineName("异常子协程")) {
                Log.d("${Thread.currentThread().name}", "我要开始抛异常了")
                throw NullPointerException("空指针异常")
            }
            for (index in 0..10) {
                launch(CoroutineName("子协程$index")) {
                    Log.d("${Thread.currentThread().name}正常执行", "$index")
                    if (index %3 == 0){
                        throw NullPointerException("子协程${index}空指针异常")
                    }
                }
            }
        }
    }
  • 创建 CoroutineScope(SupervisorJob() + exceptionHandler) 方式
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        //CoroutineExceptionHandler#handleException 方法回调
        Log.d("exceptionHandler", "${coroutineContext[CoroutineName].toString()} 处理异常 :$throwable")
    }
    //CoroutineScope 方法,SupervisorJob 方法代表子协程会自己处理异常,并不会影响其兄弟协程或者父协程
    val supervisorScope = CoroutineScope(SupervisorJob() + exceptionHandler)
    with(supervisorScope) {
        launch(CoroutineName("异常子协程")) {
            Log.d("${Thread.currentThread().name}", "我要开始抛异常了")
            throw NullPointerException("空指针异常")
        }
        for (index in 0..10) {
            launch(CoroutineName("子协程$index")) {
                Log.d("${Thread.currentThread().name}正常执行", "$index")
                if (index % 3 == 0) {
                    throw NullPointerException("子协程${index}空指针异常")
                }
            }
        }
    }

ContinuationInterceptor 协程拦截器

  • CoroutineDispatcher 也实现了 ContinuationInterceptor 接口,说明 CoroutineDispatcher 也具有拦截器的功能,所以默认情况下一旦使用了 Dispatchers.Main 等就会替换掉之前设置的自定义拦截器