带着问题学Kotlin协程一 这些问题你能回答吗

  1. ​launch返回的job和传入的CoroutineContext[Job]有啥区别。
  2. 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.协程异常是怎么在父子协程,兄弟协程之间传播的?

协程是门很大的学问,笔者也会边学边分享,遇到不对的地方还希望各位大佬不吝赐教​。

猜你喜欢

转载自juejin.im/post/7031341154905882655