2

你不知道的CoroutineContext:协程上下文大揭秘!

 5 months ago
source link: http://www.rousetime.com/2024/01/06/%E4%BD%A0%E4%B8%8D%E7%9F%A5%E9%81%93%E7%9A%84CoroutineContext%EF%BC%9A%E5%8D%8F%E7%A8%8B%E4%B8%8A%E4%B8%8B%E6%96%87%E5%A4%A7%E6%8F%AD%E7%A7%98%EF%BC%81/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

你不知道的CoroutineContext:协程上下文大揭秘!

2024.01.06

Rouse

android

 热度 1℃

协程(Coroutine)是一种并发编程技术,它允许我们在一个线程中执行多个任务,而不需要创建多个线程。协程与线程的区别在于,线程是操作系统的概念,而协程是编程语言的概念。协程可以暂停和恢复执行,而线程只能被终止。

在 Android 中,协程由 Kotlin 语言支持。Kotlin 协程库提供了丰富的 API,可以帮助我们轻松地编写并发代码。其中,CoroutineContext是一个非常重要的概念,它定义了协程的执行环境。

在本篇文章中,我们将从以下几个方面来介绍CoroutineContext的工作原理:

  • CoroutineContext的概念
  • CoroutineContext的组成
  • CoroutineContext的继承
  • CoroutineContext的注意事项

CoroutineContext的概念

CoroutineContext是一个容器,它包含了协程的所有上下文信息。这些上下文信息包括:

  • 协程的状态:协程的状态表示协程的生命周期。协程可以处于 ActiveCompletedCanceled 等状态。
  • 协程的调度策略:协程的调度策略决定了协程在哪里执行。协程可以执行在主线程、后台线程、或其他协程池中。
  • 协程的标签:协程的标签用于标识协程。
  • 协程的拦截器:协程的拦截器用于拦截协程的执行流程。
  • 协程的异常捕获:用于处理协程内部发生的未捕获异常。

CoroutineContext可以通过 coroutineContext获取。

fun main() = runBlocking {
val context = coroutineContext

println(context)
}
[CoroutineId(2), "coroutine#2":BlockingCoroutine{Active}@769c9116, BlockingEventLoop@6aceb1a5]

CoroutineContext的组成

CoroutineContext由多个组件组成,这些组件可以通过 context.get<T>() 函数来获取。

public operator fun <E : Element> get(key: Key<E>): E?

由于重新定义了get操作符,所以可以直接使用context[key]来获取对应的上下文组件元素。

  • Dispatcher:协程的调度策略。
fun main() = runBlocking {
val context = coroutineContext + Dispatchers.Main

val dispatcher = context[CoroutineDispatcher]

println(dispatcher)
}
Dispatchers.Main[missing]
  • Job:协程的状态。Job 表示协程的生命周期。
fun main() = runBlocking {
val context = coroutineContext + SupervisorJob()

val job = context[Job]

println(job)
}
SupervisorJobImpl{Active}@50675690
  • 获取协程的状态:协程的状态表示协程的生命周期。协程可以处于 ActiveCompletedCanceled 等状态。
fun main() = runBlocking {
val context = coroutineContext + SupervisorJob()

// 获取协程的状态
val job = context[Job]

// 判断协程是否处于 Active 状态
if (job?.isActive == true) {
println("协程处于 Active 状态")
}
}
协程处于 Active 状态
  • CoroutineName:协程的标签。CoroutineName 用于标识协程。
fun main() = runBlocking {
val context = coroutineContext + CoroutineName("张三")

val coroutineName = context[CoroutineName]

println(coroutineName)
}
CoroutineName(张三)
  • 添加拦截器:拦截器可以拦截协程的执行流程,例如:
  1. 在协程开始执行之前进行一些初始化操作。
  2. 在协程执行期间进行一些监控操作。
  3. 在协程执行完成之后进行一些清理操作。
class MyContinuationInterceptor : ContinuationInterceptor {

override fun interceptContinuation(continuation: Continuation<Unit>): Continuation<Unit> {
// 在协程开始执行之前进行一些初始化操作
println("MyContinuationInterceptor: 协程开始执行之前")

// 返回原始的 continuation
return continuation
}

override fun key(): CoroutineContext.Key<ContinuationInterceptor> = ContinuationInterceptor.Key
}

fun main() {
// 启动一个协程
launch(Dispatchers.IO + MyContinuationInterceptor()) {
// 执行一些耗时操作
delay(1000)
}
}

在这个示例中,协程在开始执行之前会打印一条消息:

MyContinuationInterceptor: 协程开始执行之前
  • CoroutineExceptionHandler:处理协程内部发生的未捕获异常
import kotlinx.coroutines.*

fun main() {
// 创建CoroutineExceptionHandler
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("Caught an exception: $exception")
}

// 启动一个协程,并指定CoroutineExceptionHandler
runBlocking {
val context = coroutineContext + exceptionHandler
val job = GlobalScope.launch(context) {
// 模拟一个可能抛出异常的操作
println("Coroutine is doing some work")
delay(1000)
throw CustomException("Something went wrong!")
}

// 等待协程执行结束
job.join()
}
}

// 自定义异常类
class CustomException(message: String) : Exception(message)

在这个示例中,为原有的coroutineContext增加了捕获异常的exceptionHandler,以至于协程内容抛出异常时,会被CoroutineExceptionHandler所捕获。

使用CoroutineExceptionHandler的好处在于,你可以集中处理协程内部的所有异常,而不必在每个协程体中都使用try-catch块来捕获异常。

  • EmptyCoroutineContext:一个空的 CoroutineContext。

CoroutineContext的继承

CoroutineContext支持继承。子CoroutineContext可以继承父CoroutineContext的所有组件。

fun main() = runBlocking {
val parentContext = coroutineContext + Dispatchers.Main + SupervisorJob() + CoroutineName("张三")
val childContext = parentContext + Dispatchers.IO

println(childContext)
}
[CoroutineId(2), SupervisorJobImpl{Active}@1b40d5f0, CoroutineName(张三), Dispatchers.IO]

在这个例子中,parentContext 包含 Dispatchers.MainJob()CoroutineName("张三")childContext 继承了 parentContext 的所有组件,并添加了 Dispatchers.IO,由于与Dispatchers.Main同为调度器,所以最终保留的是最后的Dispatchers.IO

CoroutineContext的注意事项

在使用CoroutineContext时,需要注意以下几点:

  • 合理选择调度器:根据任务的性质选择合适的调度器,避免在IO密集型任务中使用CPU密集型的调度器,以及反之。
  • 细致管理CoroutineContext:合理管理CoroutineContext的元素,不要过度添加不必要的元素,以免引起不必要的性能开销。
  • 异常处理:及时处理协程中的异常,可以通过在CoroutineContext中添加CoroutineExceptionHandler元素来实现。

总而言之,CoroutineContext是协程的一个重要概念。充分理解CoroutineContext的工作原理和使用方法,这样才能更好地利用CoroutineContext来控制协程的执行。

android_startup: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。

AwesomeGithub: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。

flutter_github: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。

android-api-analysis: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。

daily_algorithm: 每日一算法,由浅入深,欢迎加入一起共勉。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK