Detailed explanation of the principle of Coroutine coroutine in Android

Coroutine

foreword

A coroutine is a concurrency scheme. It is also a thought.

Coroutines in the traditional sense are single-threaded, and they consume less memory in the face of io-intensive tasks, and thus are more efficient. However, in the face of computationally intensive tasks, it is not as efficient as multi-threaded parallel computing.

Different languages ​​have different implementations for coroutines, and even the same language has corresponding implementations for operating systems on different platforms.

The coroutine of our kotlin language is the implementation of coroutines for jvm. The underlying principle is also to use java threads.

Basic knowledge

Ecological Architecture

QQ screenshot 20220228141259-16460288074381.png

Related dependencies

dependencies {
   // Kotlin
   implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.32"

   // 协程核心库
   implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
   // 协程Android支持库
   implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"
   // 协程Java8支持库
   implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.4.3"
 
   // lifecycle对于协程的扩展封装
   implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
   implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
   implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
}
​

复制代码

Why do some people always find coroutines obscure and difficult to understand?

1. There is no detailed conceptual definition of coroutines on the Internet, and each language and each system implements it differently. It can be said that there are different opinions, what kind of kernel mode user mode Barabara, it is easy to bias us

2. Various syntactic sugars of kotlin interfere with us. Such as:

  • Higher order functions
  • The source code implementation class cannot be found

So a solid basic kotlin syntax is a prerequisite for learning coroutines.

If you really can't understand the place, decompile it into java, and the final translation of java shall prevail.

What is a coroutine? What is the use?

What the coroutine in kotlin does is to flatten the asynchronous callback code, straighten it out, and synchronize the asynchronous callback code. Other than that, nothing special.

To create a coroutine is to secretly generate a series of code behind the compiler, such as a state machine .

By suspending and resuming , the state machine state flow is realized, and the nested callback code becomes as intuitive and concise as synchronous code.

It is not a threading framework, nor is it some advanced kernel mode or user mode. In fact, for our Android, it is a syntactic sugar for callback functions. . .

This article will thoroughly analyze the implementation principle of coroutines around suspending and resuming

Kotlin Function Basics Review

In Kotlin, functions are first-class citizens and have their own types

function type

fun foo(){}
//类型为 () -> Unit
fun foo(p: Int){}
//类型为 (Int) -> String

class Foo{
    fun bar(p0: String,p1: Long):Any{}
    
}
//那么 bar 的类型为:Foo.(String,Long) -> Any
//Foo就是bar的 receiver。也可以写成 (Foo,String,Long) ->Any

复制代码

function reference

fun foo(){} 
//引用是 ::foo
fun foo(p0: Int): String
//引用也是 ::foo

复制代码

Why are they the same? No way, that's how it is. When used, it can only be inferred by the compiler

val f: () -> Unit = ::foo //编译器会推断出是fun foo(){} 
val g: (Int) -> String = ::foo //推断为fun foo(p0: Int): String

复制代码

Writing with Receiver

class Foo{
    fun bar(p0: String,p1: Long):Any{}
}

复制代码
val h: (Foo,String,Long) -> Any = Foo:bar

复制代码

绑定receiver的函数引用:

val foo: Foo = Foo()
val m: (String,Long) -> Any = foo:bar

复制代码

额外知识点

val x: (Foo,String,Long) -> Any = Foo:bar
val y: Function3<Foo,String,Long,Any> = x

Foo.(String,Long) -> Any = (Foo,String,Long) ->Any = Function3<Foo,String,Long,Any>

复制代码

函数作为参数传递

fun yy(p: (Foo,String,Long)->Any){
	p(Foo(),"Hello",3L)//直接p()就能调用
    //p.invoke(Foo(),"Hello",3L) 也可以用invoke形式
}

复制代码

Lambda

就是匿名函数,它跟普通函数比是没有名字的,听起来好像是废话

//普通函数
fun func(){
   println("hello");
}
​
//去掉函数名 func,就成了匿名函数
fun(){
   println("hello");    
}
​
//可以赋值给一个变量
val func = fun(){
   println("hello");    
}
​
//匿名函数的类型
val func :()->Unit = fun(){
   println("hello");    
}
​
//Lambda表达式
val func={
   print("Hello");
}
​
//Lambda类型
val func :()->String = {
print("Hello");
"Hello" //如果是Lambda中,最后一行被当作返回值,能省掉return。普通函数则不行
}
​
//带参数Lambda
val f1: (Int)->Unit = {p:Int ->
print(p);
}
//可进一步简化为
val f1 = {p:Int ->
print(p);    
}
//当只有一个参数的时候,还可以写成
val f1: (Int)->Unit = {
   print(it);
}
​

复制代码

关于函数的个人经验总结

函数跟匿名函数看起来没啥区别,但是反编译为java后还是能看出点差异

如果只是用普通的函数,那么他跟普通java 函数没啥区别。

比如 fun a() 就是对应java方法public void a(){}

但是如果通过函数引用(:: a)来用这个函数,那么他并不是直接调用fun a()而是重新生成一个Function0

挂起函数

suspend 修饰。

挂起函数中能调用任何函数。

非挂起函数只能调用非挂起函数。

换句话说,suspend函数只能在suspend函数中调用。


简单的挂起函数展示:

//com.example.studycoroutine.chapter.CoroutineRun.kt
suspend fun suspendFun(): Int {
    return 1;
}

复制代码

挂起函数特殊在哪?

public static final Object suspendFun(Continuation completion) {
    return Boxing.boxInt(1);
}

复制代码

这下理解suspend为啥只能在suspend里面调用了吧?

想要让道貌岸然的suspend函数干活必须要先满足它!!!就是给它里面塞入一颗球。

然后他想调用其他的suspend函数,只需将球继续塞到其它的suspend方法里面。

普通函数里没这玩意啊,所以压根没法调用suspend函数。。。

读到这里,想必各位会有一些疑问:

  • question1.这不是鸡生蛋生鸡的问题么?第一颗球是哪来的?

  • question2.为啥编译后返回值也变了?

  • question3.suspendFun 如果在协程体内被调用,那么他的球(completion)是谁?

标准库给我们提供的最原始工具

public fun <T> (suspend () -> T).startCoroutine(completion: Continuation<T>) {
   createCoroutineUnintercepted(completion).intercepted().resume(Unit)
}
​
public fun <T> (suspend () -> T).createCoroutine(completion: Continuation<T>): Continuation<Unit> =
   SafeContinuation(createCoroutineUnintercepted(completion).intercepted(), COROUTINE_SUSPENDED)

复制代码

以一个最简单的方式启动一个协程。

Demo-K1

fun main() {
   val b = suspend {
       val a = hello2()
       a
  }
   b.createCoroutine(MyCompletionContinuation()).resume(Unit)
}
​
suspend fun hello2() = suspendCoroutine<Int> {
   thread{
       Thread.sleep(1000)
       it.resume(10086)
  }
}
​
class MyContinuation() : Continuation<Int> {
   override val context: CoroutineContext = CoroutineName("Co-01")
   override fun resumeWith(result: Result<Int>) {
       log("MyContinuation resumeWith 结果 = ${result.getOrNull()}")
  }
}

复制代码

两个创建协程函数区别

startCoroutine 没有返回值 ,而createCoroutine返回一个Continuation,不难看出是SafeContinuation

好像看起来主要的区别就是startCoroutine直接调用resume(Unit),所以不用包装成SafeContinuation,而createCoroutine则返回一个SafeContinuation,因为不知道将会在何时何处调用resume,必须保证resume只调用一次,所以包装为safeContinuation

SafeContinuationd的作用是为了确保只有发生异步调用时才挂起

分析createCoroutineUnintercepted

//kotlin.coroutines.intrinsics.CoroutinesIntrinsicsH.kt
@SinceKotlin("1.3")
public expect fun <T> (suspend () -> T).createCoroutineUnintercepted(completion: Continuation<T>): Continuation<Unit>

复制代码

先说结论

其实可以简单的理解为kotlin层面的原语,就是返回一个协程体。

开始分析

引用代码Demo-K1首先b 是一个匿名函数,他肯定要被编译为一个FunctionX,同时它还被suspend修饰 所以它肯定跟普通匿名函数编译后不一样。

编译后的源码为

public static final void main() {
     Function1 var0 = (Function1)(new Function1((Continuation)null) {
        int label;
​
        @Nullable
        public final Object invokeSuspend(@NotNull Object $result) {
           Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
           Object var10000;
           switch(this.label) {
           case 0:
              ResultKt.throwOnFailure($result);
              this.label = 1;
              var10000 = TestSampleKt.hello2(this);
              if (var10000 == var3) {
                 return var3;
              }
              break;
           case 1:
              ResultKt.throwOnFailure($result);
              var10000 = $result;
              break;
           default:
              throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
          }
​
           int a = ((Number)var10000).intValue();
           return Boxing.boxInt(a);
        }
​
        @NotNull
        public final Continuation create(@NotNull Continuation completion) {
           Intrinsics.checkParameterIsNotNull(completion, "completion");
           Function1 var2 = new <anonymous constructor>(completion);
           return var2;
        }
​
        public final Object invoke(Object var1) {
           return((<undefinedtype>)this.create((Continuation)var1)).invokeSuspend(Unit.INSTANCE);
        }
    });
     boolean var1 = false;
     Continuation var7 = ContinuationKt.createCoroutine(var0, (Continuation)(newMyContinuation()));
     Unit var8 = Unit.INSTANCE;
     boolean var2 = false;
     Companion var3 = Result.Companion;
     boolean var5 = false;
     Object var6 = Result.constructor-impl(var8);
     var7.resumeWith(var6);
  }

复制代码

我们可以看到先是 Function1 var0 = new Function1创建了一个对象,此时跟协程没关系,这步只是编译器层面的匿名函数语法优化

如果直接

fun main() {
   suspend {
       val a = hello2()
       a
  }.createCoroutine(MyContinuation()).resume(Unit)
}

复制代码

也是一样会创建Function1 var0 = new Function1

解答question1

继续调用createCoroutine

再继续createCoroutineUnintercepted ,找到在JVM平台的实现

//kotlin.coroutines.intrinsics.IntrinsicsJVM.class
@SinceKotlin("1.3")
public actual fun <T> (suspend () -> T).createCoroutineUnintercepted(
   completion: Continuation<T>
): Continuation<Unit> {
//probeCompletion还是我们传入completion对象,在我们的Demo就是myCoroutine
   val probeCompletion = probeCoroutineCreated(completion)//probeCoroutineCreated方法点进去看了,好像是debug用的.我的理解是这样的
   //This就是这个suspend lambda。在Demo中就是myCoroutineFun
   return if (this is BaseContinuationImpl)
       create(probeCompletion)
   else
//else分支在我们demo中不会走到
     //当 [createCoroutineUnintercepted] 遇到不继承 BaseContinuationImpl 的挂起 lambda 时,将使用此函数。
       createCoroutineFromSuspendFunction(probeCompletion) {
          (this as Function1<Continuation<T>, Any?>).invoke(it)
      }
}

复制代码
@NotNull
public final Continuation create(@NotNull Continuation completion) {
Intrinsics.checkNotNullParameter(completion, "completion");
Function1 var2 = new <anonymous constructor>(completion);
return var2;
}

复制代码

completion传入,并创建一个新的Function1,作为Continuation返回,这就是创建出来的协程体对象,协程的工作核心就是它内部的状态机,invokeSuspend函数

调用 create

@NotNull
public final Continuation create(@NotNull Continuation completion) {
	Intrinsics.checkNotNullParameter(completion, "completion");
	Function1 var2 = new <anonymous constructor>(completion);
	return var2;
}

复制代码

completion传入,并创建一个新的Function1,作为Continuation返回,这就是创建出来的协程体对象,协程的工作核心就是它内部的状态机,invokeSuspend函数

补充---相关类继承关系

20200322183101689.jpg

解答question2&3

已知协程启动会调用协程体的resume,该调用最终会来到BaseContinuationImpl::resumeWith

internal abstract class BaseContinuationImpl{
   fun resumeWith(result: Result<Any?>) {
          // This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
       var current = this
       var param = result
       while (true) {
           with(current) {
               val completion = completion!! // fail fast when trying to resume continuation without completion
               val outcome: Result<Any?> =
                   try {
                       val outcome = invokeSuspend(param)//调用状态机
                       if (outcome === COROUTINE_SUSPENDED) return
                       Result.success(outcome)
                  } catch (exception: Throwable) {
                       Result.failure(exception)
                  }
               releaseIntercepted() // this state machine instance is terminating
               if (completion is BaseContinuationImpl) {
                   // unrolling recursion via loop
                   current = completion
                   param = outcome
              } else {
                   //最终走到这里,这个completion就是被塞的第一颗球。
                   completion.resumeWith(outcome)
                   return
              }
          }
      }
  }
}

复制代码

状态机代码截取

public final Object invokeSuspend(@NotNull Object $result) {
   Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
   Object var10000;
   switch(this.label) {
   case 0://第一次进来 label = 0 
      ResultKt.throwOnFailure($result);
      // label改成1了,意味着下一次被恢复的时候会走case 1,这就是所谓的【状态流转】
      this.label = 1; 
      //全体目光向我看齐,我宣布个事:this is 协程体对象。
      var10000 = TestSampleKt.hello2(this);
      if (var10000 == var3) {
         return var3;
      }
      break;
   case 1:
      ResultKt.throwOnFailure($result);
      var10000 = $result;
      break;
   default:
      throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
   }

   int a = ((Number)var10000).intValue();
   return Boxing.boxInt(a);
}

复制代码

question3答案出来了,传进去的是create创建的那个continuation

最后再来聊聊question2,从上面的代码已经很清楚的告诉我们为啥挂起函数反编译后的返回值变为object了。

以hello2为例子,hello2能返回代表挂起的白板,也能返回result。如果返回白板,状态机return,协程挂起。如果返回result,那么hello2执行完毕,是一个没有挂起的挂起函数,通常编译器也会提醒 suspend 修饰词无意义。所以这就是设计需要,没有啥因为所以。

最后,除了直接返回结果的情况,挂起函数一定会以resume结尾,要么返回result,要么返回异常。代表这个挂起函数返回了。

调用resume意义在于重新回调BaseContinuationImpl的resumeWith,进而唤醒状态机,继续执行协程体的代码。

换句话说,我们自定义的suspend函数,一定要利用suspendCoroutine 获得续体,即状态机对象,否则无法实现真正的挂起与resume。

suspendCoroutine

我们可以不用suspendCoroutine,用更直接的suspendCoroutineUninterceptedOrReturn也能实现,不过这种方式要手动返回白板。不过一定要小心,要在合理的情况下返回或者不返回,不然会产生很多意想不到的结果

suspend fun mySuspendOne() = suspendCoroutineUninterceptedOrReturn<String> { continuation ->
    thread {
        TimeUnit.SECONDS.sleep(1)
        continuation.resume("hello world")
    }
    //因为我们这个函数没有返回正确结果,所以必须返回一个挂起标识,否则BaseContinuationImpl会认为完成了任务。 
    // 并且我们的线程又在运行没有取消,这将很多意想不到的结果
    kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
}

复制代码

而suspendCoroutine则没有这个隐患

suspend fun mySafeSuspendOne() = suspendCoroutine<String> { continuation ->
    thread {
        TimeUnit.SECONDS.sleep(1)
        continuation.resume("hello world")
    }
    //suspendCoroutine函数很聪明的帮我们判断返回结果如果不是想要的对象,自动返				
    kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
}

复制代码
public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T =
    suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
    	//封装一个代理Continuation对象
        val safe = SafeContinuation(c.intercepted())
        block(safe)
        //根据block返回结果判断要不要返回COROUTINE_SUSPENDED
        safe.getOrThrow()
    }

复制代码

SafeContinuation的奥秘

//调用单参数的这个构造方法
internal actual constructor(delegate: Continuation<T>) : this(delegate, UNDECIDED)
@Volatile
private var result: Any? = initialResult //UNDECIDED赋值给 result 
//java原子属性更新器那一套东西
private companion object {
       @Suppress("UNCHECKED_CAST")
       @JvmStatic
       private val RESULT = AtomicReferenceFieldUpdater.newUpdater<SafeContinuation<*>, Any?>(
           SafeContinuation::class.java, Any::class.java as Class<Any?>, "result"
      )
  }
​
internal actual fun getOrThrow(): Any? {
   var result = this.result // atomic read
   if (result === UNDECIDED) { //如果UNDECIDED,那么就把result设置为COROUTINE_SUSPENDED
       if (RESULT.compareAndSet(this, UNDECIDED, COROUTINE_SUSPENDED)) returnCOROUTINE_SUSPENDED
       result = this.result // reread volatile var
  }
   return when {
       result === RESUMED -> COROUTINE_SUSPENDED // already called continuation, indicate COROUTINE_SUSPENDED upstream
       result is Result.Failure -> throw result.exception
       else -> result // either COROUTINE_SUSPENDED or data <-这里返回白板
  }
}
​
public actual override fun resumeWith(result: Result<T>) {
       while (true) { // lock-free loop
           val cur = this.result // atomic read。不理解这里的官方注释为啥叫做原子读。我觉得 Volatile只能保证可见性。
           when {
             //这里如果是UNDECIDED 就把 结果附上去。
               cur === UNDECIDED -> if (RESULT.compareAndSet(this, UNDECIDED, result.value)) return
             //如果是挂起状态,就通过resumeWith回调状态机
               cur === COROUTINE_SUSPENDED -> if (RESULT.compareAndSet(this, COROUTINE_SUSPENDED, RESUMED)){
                   delegate.resumeWith(result)
                   return
              }
               else -> throw IllegalStateException("Already resumed")
          }
      }
  }

复制代码
val safe = SafeContinuation(c.intercepted())
block(safe)
safe.getOrThrow()

复制代码

Let's first review what a real hang is, that is, getOrThrow returns the "whiteboard", so when can getOrThrow return to the whiteboard? The answer is that the value of result has not been modified since it was initialized. That is to say, resumeWith has not been executed, that is, the code block (safe), the function passed in block, does not call the safe resumeWith during execution. The principle is so simple, the cas code ensures the atomicity and concurrency safety of key logic

Continuing to take Demo-K1 as an example, it is assumed here that hello2 is running in a new sub-thread , otherwise it is still not suspended.

{
   thread{
       Thread.sleep(1000)
       it.resume(10086)
  }
}

复制代码

Summarize

Finally, it can be said that to open a coroutine is to use the compiler to generate a state machine object to help us flatten the callback code and become synchronous code.

Guess you like

Origin juejin.im/post/7080421506878013470