Hello Coroutine

241 阅读5分钟

Hello Coroutine

什么是协程

  • 起源
    • 根据[高德纳]的说法, [马尔文·康威] 于1958年发明了术语“coroutine”并用于构建汇编程序[1] ,关于协程最初的出版解说在1963年发表
    • 概念
      • 协作式的子程序,允许被挂起和被恢复
      • 相关的概念,协程实现会有以下的元素
        • 生成器
        • 尾递归
        • 状态机

协程作用(个人见解)

  • 提高cpu使用效率

    • io密集型

      • 在io时候大多是磁盘的io读写操作,会有等待,导致cpu空闲,被占有但未被使用
    • cpu密集型

      • 协程对此无作用
    • 使用kotlin协程验证

      • 方案

        • 开启多个协程 充分执行CPU任务,验证 : 结果 一个协程对应一个线程,则协程未提高cpu使用效率
        • 开启多个协程 充分执行执行等待操作的任务,验证 : 结果多个协程都在同一个线程里执行,则协程能提高cpu使用效率
      • sample

        fun main(args: Array<String>) {
        //    sample1()
            runBlocking {
                launch(Dispatchers.IO) {
                    val i = 0
                    while (true) {              println("${Thread.currentThread().name} ====launch1==>$i")
                    }
                }
                launch(Dispatchers.IO) {
                    val i = 0
                    while (true) {        println("${Thread.currentThread().name} ====launch2==>$i")
                    }
                }
            }
        }
        

Kotlin中的协程

  • cps(continuation-passing style )

    • CPS把函数调用完之后接下来要执行的代码通过闭包包裹并作为函数参数调用要执行的函数。
    • java中的回调,oc的block块,js中的闭包
  • 简单例子

    • hello world
    import kotlinx.coroutines.*
    fun main() {
        hello3()
    }
    fun hello3() {
        runBlocking {
            launch {
                println("hello world!!")
            }
        }
    }
    
    • 演进

      • 协程生态.png
    • CoroutineScope作用域

      • public interface CoroutineScope {
            public val coroutineContext: CoroutineContext
        }
        
      • 管理作用域下的所有协程

        • scope.cancel()可以取消所有正在执行的协程
        • 常用的CoroutineScope ,ViewModel 有 viewModelScope,Lifecycle 有 lifecycleScope、mainScope
    • CoroutineContext(链表数据结构的接口)常用 EmptyCoroutineContext,CombinedContext等,

    @SinceKotlin("1.3")
    public interface CoroutineContext {
        public operator fun <E : Element> get(key: Key<E>): E?
        public fun <R> fold(initial: R, operation: (R, Element) -> R): R
        public operator fun plus(context: CoroutineContext): CoroutineContext =
            if (context === EmptyCoroutineContext) this else 
                context.fold(this) { acc, element ->
                    val removed = acc.minusKey(element.key)
                    if (removed === EmptyCoroutineContext) element else {
                        val interceptor = removed[ContinuationInterceptor]
                        if (interceptor == null) CombinedContext(removed, element) else {
                            val left = removed.minusKey(ContinuationInterceptor)
                            if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                                CombinedContext(CombinedContext(left, element), interceptor)
                        }
                    }
                }
        public fun minusKey(key: Key<*>): CoroutineContext
        public interface Key<E : Element>
        public interface Element : CoroutineContext {
            public val key: Key<*>
            public override operator fun <E : Element> get(key: Key<E>): E? =
                @Suppress("UNCHECKED_CAST")
                if (this.key == key) this as E else null
            public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
                operation(initial, this)
            public override fun minusKey(key: Key<*>): CoroutineContext =
                if (this.key == key) EmptyCoroutineContext else this
        }
    }
    
      • CoroutineContext使用以下元素集定义协程的行为: Job:控制协程的声明周期; CoroutineDispatcher:将任务分派到适当的线程;(Java的线程池) CoroutineName:协程的名称,可用于调试; CoroutineExceptionHandler:处理uncaught exceptions。

    img

    • Job 、 Job lifecycle
    val job = launch {
        println("test job")
    }
    
    • img
    • 不使用协程序
    class CoroutinesSample {
        fun login(username:String,pad:String,callback:(token:String)->Unit){
            try {
                TimeUnit.SECONDS.sleep(1)
            }catch (e:Exception){
                e.printStackTrace()
            }
            callback("default token")
        }
        fun getUserInfo(token:String,callback:(userName:String)->Unit){
            try {
                TimeUnit.SECONDS.sleep(1)
            }catch (e:Exception){
                e.printStackTrace()
            }
            callback("user name hgwxr")
        }
    }
    
    fun main(args: Array<String>) {
        val sample = CoroutinesSample()
        sample.apply {
            login("hgwxr","psd") {token ->
                println("received  token")
                getUserInfo(token){userName ->
                    println("received  user name: $userName")
                }
            }
        }
    }
    
    received  token:default token
    received  user name: user name hgwxr
    
    Process finished with exit code 0
    
    • 协程实现
    suspend fun getUserInfo2Coroutines(token: String): String {
            return suspendCoroutine {
                getUserInfo(token) { userName ->
                    it.resume(userName)
                }
            }
        }
    
    suspend fun login2Coroutines(username: String, psd: String): String {
            return suspendCoroutine {
                login(username, psd) { token ->
                    it.resume(token)
                }
            }
        }
     suspend fun loginCoroutines(username: String, pad: String): String {
            return coroutineScope {
                delay(1000L)
                "default token"
            }
        }
    
        suspend fun getUserInfoCoroutines(token: String): String {
            return coroutineScope {
                delay(1000L)
                "user name hgwxr"
            }
        }
    
    fun main(args: Array<String>) {
        //
        val sample = CoroutinesSample()
        sample.apply {
            runBlocking {
                val token = login2Coroutines("hgwxr", "psd")
                println("received  token:$token")
                val userName = getUserInfo2Coroutines(token)
                println("received  user name: $userName")
            }
        }
    }
    
  • 挂起实现原理

1971636467456_.pic_hd
  • suspend

  • suspend fun fun1() {
        println("test suspend....")
    }
    
  • 编译成字节码再转成java

    • @Nullable
      public static final Object fun1(@NotNull Continuation $completion) {
         String var1 = "test suspend....";
         boolean var2 = false;
         System.out.println(var1);
         return Unit.INSTANCE;
      }
      
  • Continuation

    • @SinceKotlin("1.3")
      public interface Continuation<in T> {
          public val context: CoroutineContext
          public fun resumeWith(result: Result<T>)
      }
      
  • 状态机实现挂起和恢复

    • 简单挂起协程
    suspend fun fun3(): String {
        return suspendCoroutine<String> {
            it.resume("hello world")
        }
    }
    
      @Nullable
       public static final Object fun3(@NotNull Continuation $completion) {
          boolean var1 = false;
          boolean var2 = false;
          boolean var3 = false;
          SafeContinuation var4 = new SafeContinuation(IntrinsicsKt.intercepted($completion));
          Continuation it = (Continuation)var4;
          int var6 = false;
          String var8 = "hello world";
          boolean var9 = false;
          Companion var10 = Result.Companion;
          boolean var11 = false;
          it.resumeWith(Result.constructor-impl(var8));
          Object var10000 = var4.getOrThrow();
          if (var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
             DebugProbesKt.probeCoroutineSuspended($completion);
          }
    
          return var10000;
       }
    
    • public static final void main(@NotNull String[] args) {
            Intrinsics.checkNotNullParameter(args, "args");
            BuildersKt.runBlocking$default((CoroutineContext)null, (Function2)(new Function2((Continuation)null) {
               int label;
      
               @Nullable
               public final Object invokeSuspend(@NotNull Object $result) {
                  Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
                  Object var10000;
                  switch(this.label) {
                  case 0:
                     ResultKt.throwOnFailure($result);
                     this.label = 1;
                     var10000 = CoroutinesSample3Kt.fun3(this);
                     if (var10000 == var4) {
                        return var4;
                     }
                     break;
                  case 1:
                     ResultKt.throwOnFailure($result);
                     var10000 = $result;
                     break;
                  default:
                     throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
                  }
      
                  String res = (String)var10000;
                  boolean var3 = false;
                  System.out.println(res);
                  return Unit.INSTANCE;
               }
      
               @NotNull
               public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
                  Intrinsics.checkNotNullParameter(completion, "completion");
                  Function2 var3 = new <anonymous constructor>(completion);
                  return var3;
               }
      
               public final Object invoke(Object var1, Object var2) {
                  return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
               }
            }), 1, (Object)null);
         }
      
  • Kotlin协程优点

    • Kotlin 1.3版本添加了对协程的支持
    • 支持Jetpack支持
    • 语法糖,多任务或多线程切换不在使用回调语法,结构化并发
    • 比线程更加轻量级(您可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。)
    • 内存泄漏更少 (使用结构化并发机制在一个作用域内执行多项操作。)
    • 取消操作会自动在运行中的整个协程层次结构内传播
  • 应用场景

  • 场景一

    • 异步任务执行

      • 网络请求
      • 异步任务
    • View测量会之后获取相关信息

      • view.post {
        	val height = view.measuredHeight
        	val width = view.measuredWidth
        }
        
      • suspend fun View.await() = suspendCoroutine<View> { coroutine->
        	this.post {
        		coroutine.resume(this)
        	}
        }
        
    • 点击或监听事件,权限申请,图片加载(github.com/coil-kt/coi… 等等

      • suspend fun AlertDialog.awaitClick(btnText: String): Boolean {
            return suspendCoroutine {
                setButton(
                    DialogInterface.BUTTON_POSITIVE, btnText
                ) { _, _ -> it.resume(true) }
                setButton(
                    DialogInterface.BUTTON_NEGATIVE, btnText
                ) { _, _ -> it.resume(false) }
                setButton(
                    DialogInterface.BUTTON_NEUTRAL, btnText
                ) { _, _ -> it.resume(false) }
            }
        }
        
  • 场景二

    • 同时有多网络请求或多异步任务,都有结果后再处理

      •  launch {
                        mIView?.showLoadingBackupUI()
                        val lyfyRecordItem = async {
                            fetchRecordAppInfosWithCoroutine(APP_NAME_LYFY, APP_CODE_LYFY)
                        }
                        val yjfyRecordItem = async {
                            fetchRecordAppInfosWithCoroutine(APP_NAME_YJFY, APP_CODE_YJFY)
                        }
                        val tszmRecordItem = async {
                            fetchRecordAppInfosWithCoroutine(APP_NAME_TSZM, APP_CODE_TSZM)
                        }
                        val fetchList = mutableListOf<RecordItem>().apply {
                            add(lyfyRecordItem.await())
                            add(yjfyRecordItem.await())
                            add(tszmRecordItem.await())
                        }
          }
        
  • 场景三(可以不展开讲 主要用Flow channel 类比 Rxjava)

    • 数据流 Flow
    sealed class P {
        data class OK(val data: String) : P()
        data class Process(val process: Int) : P()
        data class Error(val errorCode: Int, val msg: String) : P()
        data class Complete(val msg: String) : P()
    }
    fun getFlow(): Flow<P> {
        return flow<P> {
            emit(P.OK("hgwxr"))
            delay(1000)
            emit(P.Process(1))
            delay(1000)
            emit(P.Process(10))
            delay(1000)
            emit(P.Process(100))
            delay(50)
            emit(P.Error(-1, "erorr msg"))
            emit(P.Complete("complete"))
        }.flowOn(Dispatchers.IO)
    }
    fun main(args: Array<String>) {
        runBlocking {
            getFlow().collect {
                println("Thread name :${Thread.currentThread().name}   value :$it")
            }
        }
    }
    
    • 同一个接口多次回调返回数据(下载,翻译,socket)
      • 使用callbackFlow
    • 多异步任务相互关联 类似于rxjava的 flatmap

其他知识点

异常处理,异常取消、超时,mutext,actor,channel等

Java实现协程

github.com/kilim/kilim

github.com/offbynull/c…

github.com/puniverse/q…