首页 > 其他分享 >协程上下文的设计与使用

协程上下文的设计与使用

时间:2023-08-15 11:44:43浏览次数:38  
标签:协程 CoroutineName val 线程 IO 设计 上下文

协程上下文的设计与使用

协程是一种轻量级的并发编程模式,它可以让我们用同步的方式写出异步的代码,提高代码的可读性和性能。在协程框架中,有一个非常重要的概念,就是协程上下文(CoroutineContext)。协程上下文可以看作是一组协程运行所需的环境变量,比如调度器、异常处理器、协程名等。在本文中,我们将介绍协程上下文的设计与使用,以及它在协程中的作用和用法。

什么是协程上下文

协程上下文是一个实现了CoroutineContext接口的对象,它是一个键值对的集合,其中键是CoroutineContext.Key类型,值是CoroutineContext.Element类型。每个键值对代表了一个协程上下文的元素,比如CoroutineDispatcher、Job、CoroutineName和CoroutineExceptionHandler等。每个元素都有一个唯一的键,用于在协程上下文中查找和替换元素。例如,CoroutineDispatcher的键是ContinuationInterceptor.Key,Job的键是Job.Key等。

协程上下文的主要作用是为协程提供运行时所需的信息和功能。例如:

  • CoroutineDispatcher决定了协程在哪个线程或线程池中执行,可以实现协程的调度和切换。
  • Job表示了协程的生命周期状态和取消机制,可以实现协程的管理和控制。
  • CoroutineName为协程提供了一个可读性较高的名字,可以方便地在日志或调试器中识别协程。
  • CoroutineExceptionHandler为协程提供了一个统一的异常处理器,可以捕获并处理协程中抛出的未捕获异常。

协程上下文的接口设计

协程上下文的接口设计非常灵活和优雅,它利用了运算符重载来实现不同的功能。

例如:

协程上下文的设计与使用_线程池

  • 协程上下文可以通过+运算符来合并两个或多个协程上下文,得到一个新的包含所有元素的协程上下文。如果两个协程上下文中有相同键的元素,则后面的元素会覆盖前面的元素。例如:
// 创建一个指定调度器和名字的协程上下文
val context1 = Dispatchers.IO + CoroutineName("IO")
// 创建一个指定异常处理器和名字的协程上下文
val context2 = CoroutineExceptionHandler { _, e -> println(e) } + CoroutineName("EH")
// 合并两个协程上下文,得到一个新的包含所有元素且名字为EH的协程上下文
val context3 = context1 + context2
  • 协程上下文可以通过-运算符来移除一个或多个元素,得到一个新的不包含指定元素的协程上下文。如果要移除的元素不存在,则返回原来的协程上下文。例如:
// 创建一个指定调度器、名字和异常处理器的协程上下文
val context = Dispatchers.IO + CoroutineName("IO") + CoroutineExceptionHandler { _, e -> println(e) }
// 移除调度器和名字元素,得到一个新的只包含异常处理器元素的协程上下文
val newContext = context - Dispatchers.IO - CoroutineName("IO")
  • 协程上下文可以通过[]运算符来获取指定键对应的元素,如果不存在则返回null。例如:
// 创建一个指定调度器、名字和异常处理器的协程上下文
val context = Dispatchers.IO + CoroutineName("IO") + CoroutineExceptionHandler { _, e -> println(e) }
// 获取调度器元素
val dispatcher = context[ContinuationInterceptor.Key]
// 获取名字元素
val name = context[CoroutineName.Key]
// 获取异常处理器元素
val handler = context[CoroutineExceptionHandler.Key]
  • 协程上下文可以通过fold函数来遍历所有的元素,并对每个元素进行操作,得到一个累积的结果。例如:
// 创建一个指定调度器、名字和异常处理器的协程上下文
val context = Dispatchers.IO + CoroutineName("IO") + CoroutineExceptionHandler { _, e -> println(e) }
// 遍历所有的元素,并打印它们的键和值
val result = context.fold("") { acc, element ->
    acc + "${element.key}: ${element}\n"
}
println(result)

如何使用协程上下文

协程上下文可以在不同的场景和方法中使用,主要有以下几种:

  • 协程作用域(Coroutine Scope):

协程作用域是一个实现了CoroutineScope接口的对象,它可以用来创建和管理协程。协程作用域有一个coroutineContext属性,表示该作用域内创建的所有协程共享的协程上下文。我们可以通过构造函数或扩展函数来创建自定义的协程作用域,并指定其协程上下文。例如:

// 创建一个指定调度器和名字的协程作用域
val scope1 = CoroutineScope(Dispatchers.IO + CoroutineName("IO"))
// 创建一个指定异常处理器和名字的协程作用域
val scope2 = CoroutineScope(CoroutineExceptionHandler { _, e -> println(e) } + CoroutineName("EH"))
// 在两个不同的协程作用域中启动两个不同的协程
scope1.launch {
    // 这个协程会在IO线程中执行,并且名字为IO
    println(Thread.currentThread().name)
}
scope2.launch {
    // 这个协程会在默认线程中执行,并且名字为EH,如果抛出异常,则会被异常处理器捕获并打印
    println(Thread.currentThread().name)
    throw RuntimeException("Oops!")
}
  • 协程启动函数(Coroutine Start Function):协程启动函数是一些用来创建和启动协程的函数,比如launch、async、runBlocking等。这些函数都有一个可选的参数,用来指定创建的协程的协程上下文。如果没有指定,则使用默认的或父级的协程上下文。例如:
// 在全局作用域中启动一个指定调度器和名字的协程
GlobalScope.launch(Dispatchers.IO + CoroutineName("IO")) {
    // 这个协程会在IO线程中执行,并且名字为IO
    println(Thread.currentThread().name)
}
// 在主线程中阻塞地启动一个指定异常处理器和名字的协程
runBlocking(CoroutineExceptionHandler { _, e -> println(e) } + CoroutineName("RB")) {
    // 这个协程会在主线程中执行,并且名字为RB,如果抛出异常,则会被异常处理器捕获并打印
    println(Thread.currentThread().name)
    throw RuntimeException("Oops!")
}
  • 协程切换函数(Coroutine Switch Function):协程切换函数是一些用来在不同的调度器或线程中切换执行协程的函数,比如withContext、suspendCoroutine等。这些函数都有一个必须的参数,用来指定切换到的目标调度器或线程。这些函数会暂停当前的协程,并在目标调度器或线程中恢复执行,同时保留其他的协程上下文元素。例如:
// 在全局作用域中启动一个指定名字和异常处理器的协程
GlobalScope.launch(CoroutineName("Main") + CoroutineExceptionHandler { _, e -> println(e) }) {
    // 这个协程会在默认线程中执行,并且名字为Main,如果抛出异常,则会被异常处理器捕获并打印
    println(Thread.currentThread().name)
    // 使用withContext函数切换到IO线程中执行
    withContext(Dispatchers.IO) {
        // 这个协程会在IO线程中执行,但是仍然保留名字为Main和异常处理器
        println(Thread.currentThread().name)
        // 使用suspendCoroutine函数切换到一个自定义的线程中执行
        suspendCoroutine { continuation ->
            // 创建一个自定义的线程
            val thread = Thread {
                // 在自定义的线程中恢复协程的执行,但是仍然保留名字为Main和异常处理器
                println(Thread.currentThread().name)
                continuation.resume(Unit)
            }
            thread.start()
        }
    }
}
  • coroutineContext属性(coroutineContext Property):coroutineContext属性是一个挂起函数,它可以在任何协程中调用,用来获取当前协程的协程上下文。我们可以通过这个属性来访问或修改协程上下文中的元素。例如:
// 在全局作用域中启动一个指定名字的协程
GlobalScope.launch(CoroutineName("Main")) {
    // 获取当前协程的协程上下文
    val context = coroutineContext
    // 获取当前协程的名字元素
    val name = context[CoroutineName.Key]
    // 打印当前协程的名字
    println(name)
    // 修改当前协程的名字元素
    coroutineContext[CoroutineName.Key] = CoroutineName("New")
    // 打印修改后的协程的名字
    println(coroutineContext[CoroutineName.Key])
}

协程上下文的内置类型

协程上下文的设计与使用_协程_02

协程上下文中有一些常见的元素类型,它们都是CoroutineContext.Element的子类,实现了不同的功能。我们可以通过构造函数或工厂函数来创建这些元素,并添加到协程上下文中。以下是一些常见的元素类型:

  • CoroutineDispatcher:CoroutineDispatcher是一个抽象类,它表示了一个调度器,用来决定协程在哪个线程或线程池中执行。CoroutineDispatcher有一些内置的实现,比如Dispatchers.Default、Dispatchers.IO、Dispatchers.Main等,分别表示了默认线程池、IO线程池和主线程。我们也可以通过asCoroutineDispatcher函数将一个ExecutorService转换为一个CoroutineDispatcher,或者通过newSingleThreadContext函数创建一个单线程的CoroutineDispatcher。例如:
// 创建一个指定调度器为IO线程池的协程上下文
val context1 = Dispatchers.IO
// 创建一个指定调度器为主线程的协程上下文
val context2 = Dispatchers.Main
// 创建一个指定调度器为自定义线程池的协程上下文
val executor = Executors.newFixedThreadPool(4)
val context3 = executor.asCoroutineDispatcher()
// 创建一个指定调度器为单线程的协程上下文
val context4 = newSingleThreadContext("MyThread")
  • Job:

Job是一个接口,它表示了一个协程的生命周期状态和取消机制。Job有一些内置的实现,比如SupervisorJob、Deferred等,分别表示了一个监督任务和一个有返回值的任务。我们也可以通过Job函数创建一个普通的Job对象。Job有以下几个特点:

  • Job可以有父子关系,当一个父Job被取消时,它的所有子Job也会被取消;当一个子Job抛出异常时,它会传播给父Job,除非父Job是SupervisorJob。
  • Job可以有状态,包括New、Active、Completing、Completed、Cancelling和Cancelled等,表示了不同阶段的任务。
  • Job可以被取消,通过cancel函数或cancelAndJoin函数来取消一个任务,并等待它完成;通过join函数来等待一个任务完成;通过isActive、isCompleted和isCancelled属性来检查任务是否处于某种状态。
  • Job可以注册回调函数,通过invokeOnCompletion函数或onJoin函数来在任务完成时执行一些操作;通过cancelChildren函数或cancelChildrenAndJoin函数来取消所有子任务,并等待它们完成。
// 创建一个指定任务为普通任务的协程上下文
val context1 = Job()
// 创建一个指定任务为监督任务的协程上下文
val context2 = SupervisorJob()
// 在全局作用域中启动一个指定任务为有返回值的协程
val deferred = GlobalScope.async {
    // 这个协程会返回一个结果
    42
}
// 获取协程的任务元素
val job = deferred
// 等待协程完成,并获取结果
val result = deferred.await()
  • CoroutineName:CoroutineName是一个数据类,它表示了一个协程的名字,用于在日志或调试器中识别协程。我们可以通过CoroutineName函数创建一个CoroutineName对象,并指定一个字符串作为名字。例如:
// 创建一个指定名字为Main的协程上下文
val context = CoroutineName("Main")
// 在全局作用域中启动一个指定名字的协程
GlobalScope.launch(CoroutineName("IO")) {
    // 这个协程的名字为IO
    println(coroutineContext[CoroutineName.Key])
}
  • CoroutineExceptionHandler:CoroutineExceptionHandler是一个函数式接口,它表示了一个协程的异常处理器,用于捕获并处理协程中抛出的未捕获异常。我们可以通过CoroutineExceptionHandler函数创建一个CoroutineExceptionHandler对象,并指定一个函数作为异常处理逻辑。例如:
// 创建一个指定异常处理器为打印异常的协程上下文
val context = CoroutineExceptionHandler { _, e ->
    println(e)
}
// 在全局作用域中启动一个指定异常处理器的协程
GlobalScope.launch(context) {
    // 这个协程如果抛出异常,则会被异常处理器捕获并打印
    throw RuntimeException("Oops!")
}

标签:协程,CoroutineName,val,线程,IO,设计,上下文
From: https://blog.51cto.com/u_16175630/7086267

相关文章

  • 浅谈统一权限管理服务的设计与开发
    作者| 天地练心导读本文详细探讨了统一权限管理服务(MPS)的设计与开发,针对企业内部多平台权限管理混乱的问题,提出了一套综合RBAC、ACL、DAC权限模型的解决方案。文章从需求分析、技术选型、功能设计等方面全面介绍了MPS的构建过程。在平台&节点管理方面,MPS支持多种业务平台接入方式......
  • 基于微信小程序的微海商城设计与实现-计算机毕业设计源码+LW文档
    摘 要随着互联网技术的发展,传统的商品销售迎来了机遇,我国是个人口大国,商品的需求量大,如何推广商品的销售是企业非常关注的事情。随着电子商务多元化的发展,各种类型的商品逐渐转移到线上销售。在互联网的帮助下,带动企业打开销路,促进商品销售的可持续发展。同时,通过基于微信小程......
  • 基于微信小程序的房屋租赁小程序设计与实现-计算机毕业设计源码+LW文档
    摘要随着计算机技术的不断发展,有效地促进了社会各行业的进步,信息化逐渐运用到人们的生活中。传统模式的房屋租赁管理满足不了现代人的生活追求、服务质量和服务速度。使用管理系统进行管理,成本大大减小,同时可借助互联网强大的流量入口,使得推广的难度也大大降低。因此设计一个管理......
  • 基于微信小程序的居民疫情服务系统-计算机毕业设计源码+LW文档
    摘 要新冠病毒传播迅速,已经严重影响了人类生命安全,防控手段成为大众关注的重点。另外,随着目前信息化手段的进步,使用技术手段可以有效的对新冠疫情进行防控管理。在社区,人员多,出入流动性大,如果单靠人工进行管理,很难进行有效的统计。为此提出开发基于微信小程序的居民疫情服务系统......
  • 基于OpenCV的车牌识别系统的设计与实现
    在新世纪的大数据与人工智能高速发展之际,大数据技术以前所未有的发展速度给用户带来的各种自动化处理技术。面对如此众多的大数据与人工智能技术,非常有必要利用这些技术进行车牌识别,通过车牌识别来帮助车辆出入管理人员自动识别是否内部车辆。人工智能与大数据技术的应用,不需要车辆......
  • C/C++基础知识点——设计原则及设计模式
    如何实现模块间高内聚、低耦合?封装与抽象;添加中间层;模块化;设计思想与原则单一职责;接口隔离原则;依赖倒置;迪米特原则;多用组合少用继承;设计模式:观察者模式设计原则及设计模式六大设计原则:单一职责原则;里氏替换原则;开闭原则;依赖倒置原则;接口隔离原则;最少知识原则。......
  • 基于JAVA的毕业设计管理系统
    以往的毕业设计管理事务处理主要使用的是传统的人工管理方式,这种管理方式存在着管理效率低、操作流程繁琐、保密性差等缺点,长期的人工管理模式会产生大量的文本文件与文本数据,这对事务的查询、更新以及维护带来不少困难。随着互联网时代的到来,现如今网络的覆盖率已近非常的全面,现在......
  • 基于ASP.NET企业合同信息管理设计与实现
    系统模块结构设计企业合同信息管理系统主要分为登录、系统管理、客户信息管理、客户人员管理、商业往来管理、合同信息管理、信息查询等模块。(1)登录输入用户名称和密码,如果用户名、密码正确,则允许进入主控制平台,并根据相应的用户权限,显示相应界面;如果输入错误则给出信息提示,重新......
  • 《高级程序员 面试攻略 》通俗拟人解释 swoole的协程 和 go的协程有什么区别
    Swoole的协程和Go的协程(Goroutine)都是用于实现轻量级并发的机制,但它们有一些区别。1.语言和环境:Swoole协程是在PHP语言中实现的,而Go协程是在Go语言中实现的。因此,它们在语言和运行时环境上存在差异。1.编程模型:Swoole协程使用的是“同步风格”的编程模型,类似于传统......
  • 15项设计原则
    N+1设计。回滚设计。禁用设计。监控设计。设计多活数据中心。使用成熟的技术。异步设计。无状态系统。水平扩展而非垂直升级。设计时至少要有两步前瞻性。非核心则购买。使用商品化硬件。小构建、小发布和快试错。隔离故障。自动化。......