3.4-CoroutineScope/CoroutineContext: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,比如指定运行线程 ContinuationInterceptor
		coroutineScope {
		}

		// 能指定 CoroutineContext
		launch(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。使用场景并不多,可以根据具体场景选择使用。

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值