美文网首页kotlinKotlin
Kotlin中的协程 - Scope

Kotlin中的协程 - Scope

作者: 盛世光阴 | 来源:发表于2021-07-12 19:25 被阅读0次

    前言

    Kotlin是一种在Java虚拟机上运行的静态类型编程语言,被称之为Android世界的Swift,在GoogleI/O2017中,Google宣布Kotlin成为Android官方开发语言

    CoroutineScope

    当我们创建一个协程的时候,都会需要一个CoroutineScope,它是协程的作用域,我们一般使用它的launch函数以及async函数去进行协程的创建

    public fun CoroutineScope.launch(
        context: CoroutineContext = EmptyCoroutineContext,
        start: CoroutineStart = CoroutineStart.DEFAULT,
        block: suspend CoroutineScope.() -> Unit
    ): Job {
        val newContext = newCoroutineContext(context)
        val coroutine = if (start.isLazy)
            LazyStandaloneCoroutine(newContext, block) else
            StandaloneCoroutine(newContext, active = true)
        coroutine.start(start, coroutine, block)
        return coroutine
    }
    

    Kotlin中的协程 - CoroutineContext 中我们了解到了,在launch函数中 具有传递上下文的功能,从而生成了Job链让协程之间结构化,并且我们的CoroutineContext也是定义在CoroutineScope当中的

    public interface CoroutineScope {
        /**
         * The context of this scope.
         * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
         * Accessing this property in general code is not recommended for any purposes except accessing [Job] instance for advanced usages.
         *
         * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
         */
        public val coroutineContext: CoroutineContext
    }
    

    常用函数

    launch 启动一个协程代码块
    async 返回值Deferred,表示一个延期返回的结果
    coroutineContext 获取当前的coroutineContext
    cancel 取消掉当前Scope所对应的协程

    launch函数
    launch通常用来创建一个协程,它是CoroutineScope的函数

    public fun CoroutineScope.launch(
        context: CoroutineContext = EmptyCoroutineContext,
        start: CoroutineStart = CoroutineStart.DEFAULT,
        block: suspend CoroutineScope.() -> Unit
    ): Job {
        val newContext = newCoroutineContext(context)
        val coroutine = if (start.isLazy)
            LazyStandaloneCoroutine(newContext, block) else
            StandaloneCoroutine(newContext, active = true)
        coroutine.start(start, coroutine, block)
        return coroutine
    }
    

    这个函数主要做了以下几件事情:

    1. 将传入的context与当前Scopecontext进行运算,生成一个运算之后的context,通过newCoroutineContext函数,这个Context并不是最终协程的Context,而是它的父Context

    2.如果是非lazy模式下创建一个Job,它是StandaloneCoroutine的对象,并且在父类AbstractCoroutine中将创建的Job与上面生成的context在进行plus运算,最后才会生成此协程的Context对象

    public final override val context: CoroutineContext = parentContext + this
    

    3.然后在start中,将当前协程的Job对象指定为ParentJob,形成了父子关联,这个操作是在JobSupportinitParentJobInternal函数中实现

        internal fun initParentJobInternal(parent: Job?) {
            val handle = parent.attachChild(this)
            parentHandle = handle
        }
    

    整个过程中 Scope的工作,就是将Context进行传递,使协程之间存在父子结构化,使取消事件和异常行为进行关联

    取消协程
    Scope中也有函数可以对协程进行取消

    val scope = object : CoroutineScope {
        override val coroutineContext: CoroutineContext = Job()
    }
    
    //通过Scope创建一个协程
    val job = scope.launch() {
        Log.e("Mike", "parent job start ${this.coroutineContext[Job]}")
      //通过同样的Scope创建一个协程
        val chindJob = scope.launch() {
            Log.e("Mike", "child job start ${this.coroutineContext[Job]}")
            delay(5000)
            Log.e("Mike", "child job end ${this.coroutineContext[Job]}")
        }
        delay(4000)
        Log.e("Mike", "parent job end ${this.coroutineContext[Job]}")
    }
    Thread.sleep(1000)
    job.cancel()
    打印结果
    parent job start
    child job start
    五秒后
    child job end
    

    上面的代码中,虽然内部的协程时在外部协程中进行创建的,但是内部协程并无法通过Job去进行取消,原因就在于并没有使用到传递的CoroutineScope,使两个Job之间没有结构化关系,所以互不影响

    //job.cancel()
    scope.cancel()
    

    换成scope中的cancel就可以正常的取消,因为两个协程的Job均是ScopeJobChild

    val job = scope.launch() {
        val chindJob = launch() {
            delay(5000)
        }
        delay(4000)
    }
    Thread.sleep(1000)
    job.cancel()
    

    这时候Job之间使用CoroutineScope传递的Context,使Job之间有了关联,所以可以一起取消

    MainScope与GlobalScope

    GlobalScope中是一个EmptyCoroutineContext,其中并没有Job对象,所以也无法通过GlobalScope去取消关联的协程,所以它是进程级别的Scope

    public object GlobalScope : CoroutineScope {
        /**
         * Returns [EmptyCoroutineContext].
         */
        override val coroutineContext: CoroutineContext
            get() = EmptyCoroutineContext
    }
    

    因为Scope也是通过Job去取消的

    public fun CoroutineScope.cancel(cause: CancellationException? = null) {
        val job = coroutineContext[Job] ?: error("Scope cannot be cancelled because it does not have a job: $this")
        job.cancel(cause)
    }
    

    MianScope是由SupervisorJob和主线程调度组成的

    public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
    

    表明是无法被Cancel掉的

    private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
        override fun childCancelled(cause: Throwable): Boolean = false
    }
    
    val scope = object : CoroutineScope {
        override val coroutineContext: CoroutineContext = Job()
    }
    scope.launch {
        val response = async {
            delay(5000)
            "response data"
        }.await()
        Log.e("Mike", "async end")
        MainScope().launch {
            Log.e("Mike", "MainScope launch ")
        }
    }
    MainScope().cancel()
    }
    打印结果
    5秒后
    async end
    MainScope launch
    

    欢迎关注Mike的简书

    Android 知识整理

    相关文章

      网友评论

        本文标题:Kotlin中的协程 - Scope

        本文链接:https://www.haomeiwen.com/subject/jjvfpltx.html