首页 > 其他分享 >你不知道的CoroutineContext:协程上下文大揭秘!

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

时间:2023-12-27 22:33:34浏览次数:36  
标签:协程 val context CoroutineContext fun 上下文 Dispatchers

前言

协程(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来控制协程的执行。


标签:协程,val,context,CoroutineContext,fun,上下文,Dispatchers
From: https://blog.51cto.com/u_16175630/9004836

相关文章

  • Go简单自定义协程池
    packagemainimport( "fmt" "sync")typeTaskstruct{ ffunc()error}varwgsync.WaitGrouptypePoolstruct{ //任务通道 JobQueuechanTask //worker通道 WorkerQueuechanchanTask //worker数量 MaxWorkersint}funcNewPool(ma......
  • Golang协程池ants
    官方示例packagemainimport( "fmt" "github.com/panjf2000/ants/v2" "sync" "sync/atomic" "time")varsumint32funcmyFunc(iinterface{}){ n:=i.(int32) atomic.AddInt32(&sum,n) fmt.Printf(&q......
  • 聊一聊 C# 线程切换后上下文都去了哪里
    一:背景1.讲故事总会有一些朋友问一个问题,在Windows中线程做了上下文切换,请问被切的线程他的寄存器上下文都去了哪里?能不能给我挖出来?这个问题其实比较底层,如果对操作系统没有个体系层面的理解以及做过源码分析,其实很难说明白,这篇我们就从.NET高级调试的角度试着分析一下吧。二:寄......
  • 【已解决-实操篇】SaTokenException: 非Web上下文无法获取Request问题解决-实操篇
    在上一篇《【理论篇】SaTokenException:非Web上下文无法获取Request问题解决-理论篇》中,凯哥(凯哥Java)介绍了产生这个问题的源码在哪里,以及怎么解决的方案。没有给出实际操作步骤。本文,凯哥就通过threadLocal方案来解决。一、创建用于存放共享变量的对象代码如下:packagecom.kai......
  • 【C#】.net core 6.0 通过依赖注入注册和使用上下文服务
    给自己一个目标,然后坚持一段时间,总会有收获和感悟!请求上下文是指在Web应用程序中处理请求时,包含有关当前请求的各种信息的对象。这些信息包括请求的头部、身体、查询字符串、路由数据、用户身份验证信息以及其他与请求相关的数据。目录一、DbContext1.1、创建自定义类1.2、注册......
  • 饮冰十年-人工智能-FastAPI-01- 深入理解 Python 协程
    Python协程是一种强大的异步编程工具,可以有效地处理并发任务,提高程序性能。在这篇博客中,我们将深入探讨协程的概念、用法以及如何在Python中使用它们。一、什么是协程协程定义协程(Coroutine)是一种特殊的函数,它可以在执行中暂停并在稍后的时间点继续执行。这种能力使得我们能......
  • 聊一聊 C# 线程切换后上下文都去了哪里
    一:背景1.讲故事总会有一些朋友是不是问一个问题,在Windows中线程做了上下文切换,请问被切的线程他的寄存器上下文都去了哪里?能不能给我挖出来?这个问题其实比较底层,如果对操作系统没有个体系层面的理解以及做过源码分析,其实很难说明白,这篇我们就从.NET高级调试的角度试着分析一......
  • 从 ECMAScript 6 角度谈谈执行上下文
    大家好,我是归思君起因是最近了解JS执行上下文的时候,发现很多书籍和资料,包括《JavaScript高级程序设计》、《JavaScript权威指南》和网上的一些博客专栏,都是从ES3角度来谈执行上下文,用ES6规范解读的比较少,所以想从ES6的角度看一下执行上下文。下面我尝试用ECMAScript6规范文......
  • JavaScript 执行上下文
    一旦整个JavaScript程序运行,就会创建执行上下文。全局执行上下文已创建。它有两个组件,变量环境和变量。执行线程,它分两个阶段创建。第一阶段,是创建阶段。在创建阶段,我们为全局空间内的所有变量和函数分配内存。我们分配了一个未定义的变量。对于函数,我们实际上存储整个函数。这......
  • GCGP:Global Context and Geometric Priors for Effective Non-Local Self-Attention加
    GlobalContextandGeometricPriorsforEffectiveNon-LocalSelf-Attention*Authors:[[WooS]]初读印象comment::(GCGP)提出了一个新的关系推理模块,它包含了一个上下文化的对角矩阵和二维相对位置表示。动机普通注意力的缺点:单独处理输入图像中的每个特征,并在整个输......