- launch返回的job和传入的CoroutineContext[Job]有啥区别。
- coroutineScope和CoroutineScope有啥区别。
首先我们明确,Job,Deferred都是一个接口,scope.lunch{}返回的是Job,scope.async{}返回的是Deferred(继承自job)。
而我们平时val job = Job(),调用的是一个Job()方法,接口不可实例化。Job()方法返回的是一个JobImpl对象,看继承关系这个类最终是实现了Job接口的。
Job()方法可传入一个可空参数parentJob作为你要生成的这个的parent,parentJob.cancel()会导致这个parent下的所有子job cancel。
我们还需知道的是,scope.launch()可传入的第一个参数是CoroutineContext,这个CoroutineContext有很多子类,我们平时比较常用的Job(job,supervisorJob),CoroutineDispatcher(Dispatcher.Main等等),CoroutineExceptionHandler(用于捕捉异常),CoroutineName(常用于调试)等等组成,而CoroutineContext重载了+运算符,结果就是我们可以由job+Dispatcher.IO 或者说是job + Dispatcher.Main + myExceptionHandler 等等来表示一个coroutineContext ,而我们要获取一个CoroutineContext的Job是什么的时候也可以通过coroutineContext[Job] 来获取,其他以此类推。
问题一
@Test
fun getData() = runBlocking {
val job1 = Job()
val job2 = GlobalScope.launch(job1){
delay(2000L)
println("hello")
}
// job1.cancel()
job2.cancel()
job2.join()
}
复制代码
首先可以看上面这种写法,job1,job2 都可以取消我们启动的协程,也就是说“hello”是打印不出来的。
再看一下下面这种情况
@Test
fun getData() = runBlocking {
val job1 = Job()
val job2 = GlobalScope.launch(job1){
delay(2000L)
println("hello")
}
val job3 = GlobalScope.launch (job1){
delay(3000L)
println("world")
}
// job1.cancel()
job2.cancel()
joinAll(job2,job3)
}
复制代码
当我们调用job1.cancel()的时候我们会发现无输出,当我们调用job2.cancel()的时候,会发现只会输出“world”,换句话说,job1.cancel()取消了两个协程,job2.cancel()单单只是取消了第一个协程而已。也就是说job1更类似像是job2和job3的父job,可以管理作用域里协程的生命周期。
那此时如果我一个job也不cancel,job2抛出异常,“world”还会打印吗。
@Test
fun getData() = runBlocking {
val job1 = Job()
val job2 = GlobalScope.launch(job1) {
delay(2000L)
throw NullPointerException()
}
val job3 = GlobalScope.launch(job1) {
delay(3000L)
println("world")
}
// job1.cancel()
// job2.cancel()
joinAll(job2, job3)
}
复制代码
答案是不会打印,为什么?我们会在后续文章讨论这个问题。那如果我想打印,也就是说我job2控制的协程抛异常不去影响我job3控制协程的执行,该怎么做呢?很简单 , job1 改成 val job1 =SupervisorJob(),后续讨论原因。
但是这里有个点要注意
以下代码的输出是什么呢?
@Test
fun getData() = runBlocking {
val job = Job()
val job1 = GlobalScope.launch(job) {
delay(2000L)
throw CancellationException()
println("hello")
}
val job2 = GlobalScope.launch(job) {
delay(3000L)
println("world")
}
joinAll(job1,job2)
}
复制代码
答案是会输出“world",原因就出在这个CancellationException上面,它这个exception如果在协程里面使用,仅仅是用来返回当前协程,也就是说干掉当前协程,但是当前协程会把这个异常静默处理掉,自然这个异常抛不出来,就影响不到父协程和兄弟协程。
问题二
首先我们要先确认的一件事情,coroutineScope{} 是个什么?它其实是一个suspend方法,会将此协程在线程上挂起,换句话说,suspend会阻塞当前协程,使协程暂时挂起,但是不会使线程阻塞,如果suspend方法执行方法没在此协程所在线程上,那么这个线程就会去干其他事,直到suspend方法结束,然后协程resumed,线程进行调度,再继续执行挂起点也就是那个suspend方法后的代码,具体流程后面文章剖析。
CoroutineScope我们都知道,协程作用域,常见的类似lifecycleScope,viewModelScope。那我们看看coroutineScope()这个方法是用来做什么的。
@Test
fun getData() = runBlocking {
val job = GlobalScope.launch {
coroutineScope {
val job1 = launch {
delay(3000L)
println("job1")
}
val job2 = launch {
delay(2000L)
println("job2")
}
}
val job3 = launch {
println("job3")
}
}
job.join()
}
复制代码
上面这段代码的结果很明显 job2 job1 job3
这个couroutineScope()方法感觉像是一种协程作用域构建器,也就是说在该构建器里面的代码会在couroutineScope()方法执行的协程进行协程阻塞,就是我这构建器里面的代码要确保执行完毕,你这个协程才能往下走。但是coroutineScope()方法和supervisorScope()方法要区别开,它们之间的区别就是类似Job()和supervisorJob()的区别,兄弟协程抛异常是否互相影响。
这里可以预告学协程二的相应问题
3.协程上下文是怎么在父子协程传播的?
4.协程异常是怎么在父子协程,兄弟协程之间传播的?
协程是门很大的学问,笔者也会边学边分享,遇到不对的地方还希望各位大佬不吝赐教。