当前位置: 首页 > news >正文

3.4-CoroutineScope/CoroutineContext:coroutineScope() 和 supervisorScope()

文章目录

  • coroutineScope()
  • supervisorScope()
  • 总结

coroutineScope()

coroutineScope() 和我们创建协程时的 CoroutineScope 名字是相同的,实际上它们也确实有所关联,为了方便理解我们先说下 coroutineScope() 是怎样的效果。

我们在使用 coroutineScope() 时 IDE 会有提示官方文档:

在这里插入图片描述

创建一个 CoroutineScope 然后在这个 scope 里面执行它的 block 代码块里的代码,这个 CoroutineScope 会继承当前的 coroutineContext,以及用当前的 Job 来作为内部的父 Job。

按照上面的解释,可以发现 coroutineScope() 也是会创建一个子协程,和用 launch 创建子协程很像:

fun main() = runBlocking {val scope = CoroutineScope(EmptyCoroutineContext)scope.launch {coroutineScope {// block 代码块的 this 是一个新的 CoroutineScope// 代码块里的代码会在新的 CoroutineScope 里执行// 在这个代码块使用的 coroutineContext 和外面 scope.launch 代码块的 coroutineContext 是一样的// 不同的地方只有 coroutineScope() 创建了一个子 Job }launch {// 子协程的也是一个新的 CoroutineScope// 也是一个子 Job}}delay(10000)
}

coroutineScope() 和 launch 有区别的地方

  • coroutineScope() 不能像 launch 一样定制 CoroutineContext
fun main() = runBlocking {val scope = CoroutineScope(EmptyCoroutineContext)scope.launch {// 不能指定 CoroutineContext,比如指定运行线程 ContinuationInterceptorcoroutineScope {}// 能指定 CoroutineContextlaunch(Dispatchers.IO) {}}delay(10000)
}
  • coroutineScope() 是一个挂起函数,运行时是串行执行的,会等待它内部的代码块(包括它里面的子协程)都执行完成才返回继续后续的代码;launch 启动协程是并行执行的,启动协程后就继续执行后续的代码
fun main() = runBlocking {val scope = CoroutineScope(EmptyCoroutineContext)scope.launch {// 挂起函数,等待代码块的代码执行完后才继续往后执行val startTime = System.currentTimeMillis()coroutineScope {delay(1000)println("Duration within coroutineScope: ${System.currentTimeMillis() - startTime}")}println("Duration of coroutineScope: ${System.currentTimeMillis() - startTime}")}delay(10000)	
}输出结果:
Duration within coroutineScope: 1006
Duration of coroutineScope: 1007 // 等待执行完后才执行后续代码fun main() = runBlocking {val scope = CoroutineScope(EmptyCoroutineContext)scope.launch {// 启动协程,launch 执行后就继续往后执行不等待val startTime = System.currentTimeMillis()launch {delay(1000)println("Duration within launch: ${System.currentTimeMillis() - startTime}")				}println("Duration of launch: ${System.currentTimeMillis() - startTime}")			}delay(10000)	
}输出结果:
Duration of launch: 0 // launch 调用完就继续执行后续代码
Duration within launch: 1010 
  • coroutineScope() 有返回值,launch 没有返回值
fun main() = runBlocking {val scope = CoroutineScope(EmptyCoroutineContext)scope.launch {val result = coroutineScope {val deferred1 = async { "Hello" }val deferred2 = async { "coroutine!" }"${defferred1.await()} ${deferred2.await()}"}println(result)}delay(10000)
}输出结果:Hello coroutine!

需要注意的是,我们用 coroutineScope() 和 launch 对比并不是想着 [什么时候用 coroutineScope() 替换 launch 使用],只是因为它们内部的工作原理有很大的相似之处,但它们的应用场景是完全不同的

实际上最适合和 coroutineScope() 做对比的是,在协程内部啥都不用

fun main() = runBlocking {val scope = CoroutineScope(EmptyCoroutineContext)scope.launch {// 这两者没啥区别,这么使用肯定优先选不用 coroutineScope() 执行的函数someFunc()coroutineScope {someFunc()	}}delay(10000)	
}

如果我们在协程调用一个函数 someFunc(),这时候直接在协程调用和用 coroutineScope() 包着调用函数相比,直接调用 someFunc() 函数会更合理。

但如果 someFunc() 是一个挂起函数,而且又想在挂起函数启动协程时,coroutineScope() 就派上用场了:

fun main() = runBlocking {val scope = CoroutineScope(EmptyCoroutineContext)scope.launch {someFunc()}delay(10000)	
}// 是一个挂起函数,挂起函数是没有 CoroutineScope 的
// 这里就可以用 coroutineScope() 给挂起函数提供一个 CoroutineScope 可以启动协程的环境
private suspend fun someFunc() = coroutineScope {launch {}
}

coroutineScope() 的第一个使用场景是:当我们想在挂起函数里启动协程但又没有启动协程的环境时,就用 coroutineScope() 提供 CoroutineScope 的环境

coroutineScope() 的第二个使用场景是: coroutineScope() 可以用来封装完整的功能逻辑,在抛出异常时正确捕获异常能让整个外部协程继续正常工作而不会导致整个协程树崩溃

fun main() = runBlocking {val scope = CoroutineScope(EmptyCoroutineContext)scope.launch {// 正确处理异常,让整个协程树能正常运行val result = try {coroutineScope {val deferred1 = async { "Hello" }val deferred2: Deferred<String> = async { throw RuntimeException("Error!") }"${defferred1.await()} ${deferred2.await()}"}} catch (e: Exception) {e.message}println(result)}delay(10000)
}输出结果:Error!

supervisorScope()

supervisorScope() 和 coroutineScope() 在功能上是相同的,不同的是 supervisorScope() 创建子协程是一个类似于 SupervisorJob 的子 Job。关于 SupervisorJob 可以查看之前写的文章 2.4-结构化并发:协程的结构化异常管理 具体了解。

但大多数时候挂起函数里启动的各个子协程通常对于挂起函数的总流程都是有用的,在 coroutineScope() 抛异常通常来说整个挂起函数就失去价值了,它就应该坏掉抛异常。所以 supervisorScope() 的适用场景并不多

总结

coroutineScope() 会创建一个子协程,和使用 launch 创建子协程很像。

我们用 coroutineScope() 和 launch 对比并不是想着 [什么时候用 coroutineScope() 替换 launch 使用],只是因为它们内部的工作原理有很大的相似之处,但它们的应用场景是完全不同的

coroutineScope() 和 launch 有以下不同

  • coroutineScope() 不能像 launch 一样定制 CoroutineContext

  • coroutineScope() 是一个挂起函数,运行时是串行执行的;launch 启动协程是并行执行的

  • coroutineScope() 有返回值,launch 没有返回值

coroutineScope() 的使用场景主要有以下两个

  • 在挂起函数提供 CoroutineScope 启动协程的环境

  • 封装完整的功能逻辑

supervisorScope() 和 coroutineScope() 在功能上是相同的,不同的是 supervisorScope() 创建子协程是一个类似于 SupervisorJob 的子 Job。使用场景并不多,可以根据具体场景选择使用。


http://www.mrgr.cn/news/8025.html

相关文章:

  • hackit 2018
  • QT上位机学习路线(C++)
  • PHP反序列化二
  • ArcGIS Pro基础:如何将数据和引用地图样式一起打包分享
  • Golang | Leetcode Golang题解之第367题有效的完全平方数
  • rust api接口开发(以登陆和中间件鉴权为例)
  • MidJourney付费失败的原因以及失败后如何取消或续订(文末附MidJourney,GPT-4o教程)
  • 如何在Spring Boot应用中加载和使用TensorFlow模型
  • HTML5 浏览器支持
  • 如何使用ssm实现基于JAVA的网上药品售卖系统
  • java通过JDBC连接mysql和postgres数据库实现读写数据
  • 利用API返回值实现商品信息的自动化更新
  • git提交项目,报403无权限
  • 福特汽车削减电动车计划,聚焦成本控制
  • 支持2.4G频秒变符合GB42590的标准的飞行器【无人机GB42590发射端】
  • Springboot统一给redis缓存的Key加前缀
  • 【网络】传输层协议——TCP协议(初阶)
  • Java JNA调用C函数常见问题及解决方法
  • Elasticsearch核心
  • 开发者学习类网站