zoukankan      html  css  js  c++  java
  • 协程

    协程

    协程是什么?

    • 协程是可以由程序自行控制挂起、恢复的程序。
    • 协程可以用来实现多任务的协作执行。
    • 协程可以用来解决异步任务控制流的灵活转移。

    协程的作用?

    • 协程可以让异步代码同步化。
    • 协程可以降低异步程序的设计复杂度。
    • 挂起和恢复可以控制执行流程的转移。
    • 异步逻辑可以用同步代码的形式写出。
    • 同步代码比异步代码更加灵活,更容易实现复杂业务。

    线程和协程

    Kotlin协程只是一个“线程框架”?

    • 运行在线程上的框架不一定就是“线程框架”,例如所有框架
    • 支持线程切换的框架也不一定就是“线程框架”,例如OkHttp

    Kotlin协程

    • 官方协程框架(框架级别的支持)

      Job

      调度器

      作用域

    • Kotlin标准库(语言级别的支持)

      协程上下文

      拦截器

      挂起函数


    协程的分类

    按调用栈

    • 有栈协程:每个协程会分配单独的调用栈,类似线程的调用栈。可以在任意函数嵌套中挂起,例如Lua Coroutine
    • 无栈协程:不会分配单独的调用栈,挂起点状态通过闭包或者对象保存。只能在当前函数中挂起,例如Python Generator

    按调用关系

    • 对称协程:调度权可以转移给任意协程,协程之间的关系是对等的。
    • 非对称协程:调度权只能转移给调用自己的协程,协程存在父子关系。

    协程的常见实现

    协程:挂起和恢复

    • Python Generator

    • Go routine

      每个Go Routine都是并发或者并行执行

      无Buffer的channel写时会挂起,直到读取,反之依然

      GoRoutine 可以认为是一种有栈对称协程的实现

    • Lua Coroutine

    • async/await

      很多语言都支持

      可以多层嵌套,但是必须是async function

      async/await是一种无栈非对称的协程实现

      是目前语言支持最广泛的特性

    • ...

    协程基本要素

    挂起函数

    • 挂起函数:以suspend修饰的函数
    • 挂起函数只能在其他挂起函数或者协程中调用
    • 挂起函数调用时包含了协程“挂起”的语义,挂起函数的调用处称为“挂起点”,刮起的时候主调用流程就挂起了。
    • 挂起函数返回时则包含了协程“恢复”的语义,恢复的时候主调用流程就恢复了。

    Continuation

    有栈协程可以把挂起点的状态保存在栈当中,但是无栈协程的话是会保存在闭包当中,而在Kotlin当中是会通过Continuation保存挂起点的状态。

    @SinceKotlin("1.3")
    public interface Continuation<in T> {
        /**
         * The context of the coroutine that corresponds to this continuation.
         */
        public val context: CoroutineContext
    
        /**
         * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
         * return value of the last suspension point.
         */
        public fun resumeWith(result: Result<T>)
    }
    
    
    @SinceKotlin("1.3")
    @InlineOnly
    public inline fun <T> Continuation<T>.resume(value: T): Unit =
        resumeWith(Result.success(value))
    
    @SinceKotlin("1.3")
    @InlineOnly
    public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
        resumeWith(Result.failure(exception))
    

    这里的话可以看到,挂起和恢复都是伴随着正常结果的返回和异常结果的返回。

    其实调用一个挂起函数是需要传入一个Continuation的,只是这是编译器已经完成的事情,而只有挂起函数和协程才会拥有一个Continuation。

    挂起函数类型

    比如:suspend() -> Unit、suspend(String) -> String

    • 将回调转写成挂起函数:

      真正的挂起时必须异步调用resume,切换到其他县城resume或者是单线程事件循环异步执行;而如果在suspendCoroutine中直接调用resume也算是没有挂起。

      使用suspendCoroutine获取挂起函数的Continuation,而回调成功的分支使用Continuation.resume(value);而回调失败则使用Continuation.resumeWithException(e)

    协程的创建

    • 首先,协程是一段可执行的程序
    • 协程的创建通常需要一个函数 suspend function
    • 协程的创建也需要一个API。 createCoroutine、startCoroutine

    suspend函数本身执行的时候需要一个Continuation实例在恢复时调用,即此处的参数:completion;返回值Continuation 则是创建出来的协程的载体,receiver;suspend函数会传给该实例作为协程的实际执行体。

    @SinceKotlin("1.3")
    @Suppress("UNCHECKED_CAST")
    public fun <T> (suspend () -> T).createCoroutine(
        completion: Continuation<T>
    ): Continuation<Unit> =
        SafeContinuation(createCoroutineUnintercepted(completion).intercepted(), COROUTINE_SUSPENDED)
    
    @SinceKotlin("1.3")
    @Suppress("UNCHECKED_CAST")
    public fun <T> (suspend () -> T).startCoroutine(
        completion: Continuation<T>
    ) {
        createCoroutineUnintercepted(completion).intercepted().resume(Unit)
    }
    

    协程上下文

    • 协程执行过程中需要携带数据
    • 索引是CoroutineContext.Key
    • 元素是CoroutineContext.Element

    拦截器

    • 拦截器CoroutineIntereptor是一类协程上下文元素
    • 可以对协程上下文所在的协程Continuation进行拦截与篡改。
    @SinceKotlin("1.3")
    public interface ContinuationInterceptor : CoroutineContext.Element {
      public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
      //...
    }
    
    //标准库中的SuspendLambda就是
    @SinceKotlin("1.3")
    // Suspension lambdas inherit from this class
    internal abstract class SuspendLambda(
        public override val arity: Int,
        completion: Continuation<Any?>?
    ) : ContinuationImpl(completion), FunctionBase<Any?>, SuspendFunction {
        constructor(arity: Int) : this(arity, null)
    
        public override fun toString(): String =
            if (completion == null)
                Reflection.renderLambdaToString(this) // this is lambda
            else
                super.toString() // this is continuation
    }
    
    
    //SuspendLambda对应的就是下面suspend包含的部分
    suspend{
      a()
    }.startCoroutine(...)
    
    //而如果suspend里面有调用了挂起函数,在调用的过程中还会用SafeContinuation包装SuspendLambda
    

    Continuaion的执行

    • SafeContinuation的作用就是确保

      • resume只被调用一次

      • 如果在当前线程调用栈上直接调用则不会挂起。

    • 拦截Continuaion

      会用Intercepted先将SuspendLambda包装一次,每次SafeContinuation调用resume的时候会先调用Intercepted返回的Continuation的resume。

      • SafeContinuation仅在挂起点的时候出现
      • 拦截器在每次恢复或者执行协程体的时候调用
      • SuspendLambda是协程函数体

    协程挂起恢复要点

    • 协程体内的代码都是通过Continuation.resumeWith调用
    • 每调用一次lable加1,每一个挂起点对应于一个case分支
    • 挂起函数返回COUROUTINE_SUSPENDED的时候才会挂起

    协程的线程调度

    协程改造的异步程序

  • 相关阅读:
    September 17th 2016 Week 38th Saturday
    【2016-09-16】UbuntuServer14.04或更高版本安装问题记录
    September 16th 2016 Week 38th Friday
    September 11th 2016 Week 38th Sunday
    September 12th 2016 Week 38th Monday
    September 10th 2016 Week 37th Saturday
    September 9th 2016 Week 37th Friday
    c++暂停
    八皇后问题
    ( 转转)Android初级开发第九讲--Intent最全用法(打开文件跳转页面等)
  • 原文地址:https://www.cnblogs.com/chen-ying/p/13227027.html
Copyright © 2011-2022 走看看