Reference: www.jianshu.com/u/a324daa6f…

Reference – Throw line: rengwuxian.com/kotlin-coro…

Explore the Kotlin coroutine mechanism

  • What is a coroutine
  • Use of coroutines
  • Start of coroutines
  • Coroutine hanging, recovery principle reverse analysis

1. What is coroutine

Scenario 1: Asynchronous callback nesting

  • Regular writing
// The client makes three network asynchronous requests in sequence and updates the UI with the final results
request1(parameter) { value1 ->
    request2(value1) { value2 ->
        request3(value2) { value3 ->
            updateUI(value3)            
        } 
    }              
}
Copy the code

This structure of code is extremely bad to read and maintain. I affectionately refer to nested coupling of multiple callbacks as “callback hell.”

  • Writing coroutines
GlobalScope.launch(Dispatchers.Main){
  val value1 = request1()
  val value2 = request2(value1)
  valValue3 = request2(value2) updateUI(value3)}suspend request1( )
suspend request2(..)
suspend request3(..)
Copy the code

Scenario 2: Concurrent flow control

  • Regular writing
// The client makes three network asynchronous requests in sequence and updates the UI with the final result
fun request1(parameter) { value1 ->
    request2(value1) { value2 ->
      this.value2=value2   
        if(request3){
         updateUI()       
      }
    } 
  request3(value2) { value3 ->
      this.value3=value3                
        if(request2) {
        updateUI()
      }     
    }                                  
}

fun updateUI(a)
Copy the code
  • Coroutines writing
GlobalScope.launch(Dispatchers.Main){
   val value1 =    request1()
   val deferred2 = GlobalScope.async{request2(value1)}
   val deferred3 = GlobalScope.async{request3(value2)}
   updateUI(deferred2.await(),deferred3.await())
}

suspend request1( )
suspend request2(..)
suspend request3(..)
Copy the code

The purpose of coroutines is to allow better collaboration between multiple tasks and solve asynchronous callback nesting. Ability to orchestrate code synchronously for asynchronous work. Make asynchronous code as intuitive as synchronous code. It is also a solution for concurrent flow control.

Coroutines are designed to simplify complex code written using “async + callback” into something that looks like synchronous writing, weakening the concept of threads (further abstraction of thread operations).

2. Usage of coroutines

Introduce gradle dependencies

/ / in kotlin project cooperate jetpack architecture introduced coroutines API 'androidx. Lifecycle: lifecycle - viewmodel - KTX: 2.2.0' API 'androidx. Lifecycle: lifecycle - runtime - KTX: 2.2.0' API 'androidx. Lifecycle: lifecycle - livedata - KTX: 2.2.0' / / in kotlin project but not the jetpack architecture project introduced coroutines API "org. Jetbrains. Kotlinx: kotlinx coroutines -- core: 1.2.1" API 'org. Jetbrains. Kotlinx: kotlinx coroutines - android: 1.1.1'Copy the code

Common methods for creating coroutines

/ / create coroutines, by Dispatchers. IO, MAIN, Unconfined specified coroutines running threads
val job:Job =GlobalScope.launch(Dispatchers.Main)
valDeffered: deffered = GlobalScope. Async (Dispatchers. IO)Copy the code

Job: The return value of the coroutine builder. Think of Job as the coroutine object itself, containing the control methods for the coroutine.

Deffered is a subclass of Job and actually adds an await method. Can temporarily suspend the current coroutine, pausing further execution. When the await method returns a value, the coroutine is resumed and execution continues

methods instructions
start() Start coroutines manually
join() Wait for the coroutine to finish executing
cancel() Cancel a coroutine

Start of coroutines

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope. () - >Unit
): Job{
  val newContext = newCoroutineContext(context)
  val coroutine =  StandaloneCoroutine(newContext, active = true)
  coroutine.start(start, coroutine, block)
}
Copy the code

CoroutineContext – the context of a coroutine, which is a key-value data structure

CoroutineContext List
get(key: Key): E get(int index)
plus(context: Element) add(int index, E element)
minusKey(key: Key<*>) remove(E element)

A simple example

object CoroutineScene {

    private const val TAG = "CoroutineScene"


    fun startScene1(a){
        GlobalScope.launch (Dispatchers.Main){
            Log.i(TAG,"startScene work on ${Thread.currentThread().name}")
            val result1 = request1()
            val result2 = request2(result1)
            val result3 = request3(result2)
            updateUI(result3)
        }
    }


    fun startScene2(a){
        GlobalScope.launch (Dispatchers.Main){
            Log.i(TAG,"startScene work on ${Thread.currentThread().name}")
            val result1 = request1()


            val deferred2 = GlobalScope.async { request2(result1) }
            val deferred3 = GlobalScope.async { request3(result1) }
            // Cannot call await alone
            updateUI(deferred2.await(),deferred3.await())
        }
    }



    private fun updateUI(result: String) {
        Log.i(TAG,"updateUI work on ${Thread.currentThread().name}")
        Log.i(TAG,"result: $result")}private fun updateUI(result2:String,result3: String) {
        Log.i(TAG,"updateUI work on ${Thread.currentThread().name}")
        Log.i(TAG,"result: ${result2}--${result3}")}Is the suspend keyword useful?
    // Delay is an asynchronous task. How does delay coroutine execute down?
    suspend fun request1(a):String{
        delay(2*1000)   // A delay of 2 seconds will not suspend the thread, but will suspend the current coroutine

        Log.i(TAG,"request1 work on ${Thread.currentThread().name}")
        return "result from request1"
    }

    suspend fun request2(request:String):String{
        delay(2*1000)   // Delay 2 seconds
        Log.i(TAG,"request2 work on ${Thread.currentThread().name}")
        return "result from request2"
    }

    suspend fun request3(request:String):String{
        delay(2*1000)   // Delay 2 seconds
        Log.i(TAG,"request3 work on ${Thread.currentThread().name}")
        return "result from request3"}}Copy the code

CoroutineDispatcher a thread scheduler run by a coroutine

Coroutine scheduler

model instructions
Dispatchers.IO Displays the thread on which the specified coroutine runs, the IO thread
Dispatchers.Main Specifies that the coroutine runs on the main thread
Dispatchers.Default By default, a thread is started when Ctrip is started
Dispatchers.Unconfined If not specified, the coroutine will run on the current thread, depending on the thread in which the coroutine was suspended

CoroutineStart – Start mode

The default is DEAFAULT, which is to create on the start; Another is LAZY, which means you call startup when you need it

model instructions
CoroutineStart().DEAFAULT Mode mode, create start coroutine, can be cancelled at any time
ATOMIC In automatic mode, it can be started immediately after being created, but cannot be cancelled before being started
LAZY Lazy start mode, which starts only when the start method is called

3. Coroutine suspension, recovery principle reverse analysis

Hang up function

Methods decorated by the keyword suspend At compile time, the compiler modifies the signature of the method. Includes return values, modifiers, input parameters, and method body implementations. Coroutines are suspended by suspending code implemented in functions.

//kotlin
suspend fun request(a): String {
     delay(2 * 1000)     //suspend fun()
     println("after delay")
     return "result from request"
}
Copy the code

After switching to Java

//java
public static final Object request(Continuation completion) {
  ContinuationImpl requestContinuation = completion;
        if ((completion.label & Integer.MIN_VALUE) == 0) 
            requestContinuation = new ContinuationImpl(completion) {
                @Override
                Object invokeSuspend(Object o) {
                    label |= Integer.MIN_VALUE;
                    return request(this);   //3. Request again}}; } switch (requestContinuation.label) { case0: {  For the first time, execute the method and change the tag to 1
                requestContinuation.label = 1;
                //2. Execute the delay operation and put the asynchronous listener in it, so we can see that the coroutine is actually a method suspension
                Object delay = DelayKt.delay(2000, requestContinuation); 
                if (delay == COROUTINE_SUSPENDED) {
                    returnCOROUTINE_SUSPENDED; }}}//4. Execute the following method
  System.out.println("after delay")
  return "result from request";
}
Copy the code

Coroutine suspension and coroutine recovery

The core of coroutines is suspend —- restore, suspend — the essence of recovery is return & callback callback

Simulate the entire process of suspend and resume

object CoroutineScene2 {
    
    private  val TAG :String = "CoroutineScene2"
    
    suspend fun request2(a):String{
        delay(2*1000);
        Log.i(TAG,"request2 completed")
        return "result from request2"; }}Copy the code

Java

public class CoroutineScene2_decompiled {

    private static final String TAG = "CoroutineScene2";


    //1. Suspend the process
    public static final Object request2(Continuation preCallback) {
        ContinuationImpl request2Callback;

        if(! (preCallbackinstanceof ContinuationImpl) || (((ContinuationImpl) preCallback).label & Integer.MAX_VALUE) == 0) {
            request2Callback = new ContinuationImpl(preCallback) {
                @Override
                public Object invokeSuspend(@NotNull Object resumeResult) {
                    this.result = resumeResult;
                    this.label |= Integer.MAX_VALUE;
                    return request2(this); }}; }else {
            request2Callback = (ContinuationImpl) preCallback;
        }
        switch (request2Callback.label){
            case 0:
                Object delay = DelayKt.delay(2000,request2Callback);
                if(delay == IntrinsicsKt.getCOROUTINE_SUSPENDED()){
                    return IntrinsicsKt.getCOROUTINE_SUSPENDED();
                }
        }
        Log.i(TAG,"request2 comleted");
        return "result from request 2";
    }


    static abstract class ContinuationImpl<T> implements Continuation<T> {

        Continuation preCallback;
        int label;
        Object result;

        public ContinuationImpl(Continuation preCallback) {
            this.preCallback = preCallback;
        }

        @NotNull
        @Override
        public CoroutineContext getContext(a) {
            return preCallback.getContext();
        }


        //2. Restore process
        @Override
        public void resumeWith(@NotNull Object result) {
            Object suspend = invokeSuspend(result);
            if(suspend == IntrinsicsKt.getCOROUTINE_SUSPENDED()){
                return;
            }
            preCallback.resumeWith(suspend);
        }

        public abstract Object invokeSuspend(@NotNull Object resumeResult); }}Copy the code

kotliin

object CoroutineScene2 {

    private  val TAG :String = "CoroutineScene2"


    suspend fun request1(a):String{
        val request2 :String  = request2();
        return "result from request1" + request2
    }

    suspend fun request2(a):String{
        delay(2*1000);
        Log.i(TAG,"request2 completed")
        return "result from request2"; }}Copy the code

Java

public class CoroutineScene2_decompiled {

    private static final String TAG = "CoroutineScene2";
    //1. Suspend the process
    public static final Object request1(Continuation preCallback) {
        ContinuationImpl request1Callback;

        if(! (preCallbackinstanceof ContinuationImpl) || (((ContinuationImpl) preCallback).label & Integer.MAX_VALUE) == 0) {
            request1Callback = new ContinuationImpl(preCallback) {
                @Override
                public Object invokeSuspend(@NotNull Object resumeResult) {
                    this.result = resumeResult;
                    this.label |= Integer.MAX_VALUE;
                    Log.i(TAG,"request1 has resumed");
                    return request1(this); }}; }else {
            request1Callback = (ContinuationImpl) preCallback;
        }
        switch (request1Callback.label){
            case 0:
                //Object delay = DelayKt.delay(2000,request2Callback);
                Object request2= request2(request1Callback);
                if(request2 == IntrinsicsKt.getCOROUTINE_SUSPENDED()){
                    Log.i(TAG,"request1 has suspended");
                    return IntrinsicsKt.getCOROUTINE_SUSPENDED();
                }
        }
        Log.i(TAG,"request2 completed");
        return "result1 from request1 " + request1Callback.result;
    }



    //1. Suspend the process
    public static final Object request2(Continuation preCallback) {
        ContinuationImpl request2Callback;

        if(! (preCallbackinstanceof ContinuationImpl) || (((ContinuationImpl) preCallback).label & Integer.MAX_VALUE) == 0) {
            request2Callback = new ContinuationImpl(preCallback) {
                @Override
                public Object invokeSuspend(@NotNull Object resumeResult) {
                    this.result = resumeResult;
                    this.label |= Integer.MAX_VALUE;
                    Log.i(TAG,"request2 has resumed");
                    return request2(this); }}; }else {
            request2Callback = (ContinuationImpl) preCallback;
        }
        switch (request2Callback.label){
            case 0:
                Object delay = DelayKt.delay(2000,request2Callback);
                if(delay == IntrinsicsKt.getCOROUTINE_SUSPENDED()){
                    Log.i(TAG,"request2 has suspended");
                    return IntrinsicsKt.getCOROUTINE_SUSPENDED();
                }
        }
        Log.i(TAG,"request2 comleted");
        return "result from request 2";
    }


    static abstract class ContinuationImpl<T> implements Continuation<T> {

        Continuation preCallback;
        int label;
        Object result;

        public ContinuationImpl(Continuation preCallback) {
            this.preCallback = preCallback;
        }

        @NotNull
        @Override
        public CoroutineContext getContext(a) {
            return preCallback.getContext();
        }


        //2. Restore process
        @Override
        public void resumeWith(@NotNull Object result) {
            Object suspend = invokeSuspend(result);
            if(suspend == IntrinsicsKt.getCOROUTINE_SUSPENDED()){
                return;
            }
            preCallback.resumeWith(suspend);
        }

        public abstract Object invokeSuspend(@NotNull Object resumeResult); }}Copy the code

Call:

val callback = Continuation<String>(Dispatchers.Main){result->
      Log.i(TAG,result.getOrNull())
 }
CoroutineScene2_decompiled.request1(callback)
Copy the code

Coroutines review

  • What is a coroutine

    • Coroutines are a solution, a solution to the concept of nested, concurrent, weak threads. Allows for better collaboration between multiple tasks and the ability to orchestrate code synchronously for asynchronous work. Write asynchronous code as intuitively as synchronous code.
  • Start of coroutines

    • According to create coroutines specified scheduler HandlerDispatcher, DefaultScheduler, UnconfinedDispatcher to perform a task, to determine coroutines in blocks of code to run on the thread.
  • Suspend coroutine, resume

    • The essence of the method is suspend, resume. The essence is return +callback.
    • Callbacks between methods are handled with compile-time transformations, which makes it intuitive to write sequential asynchronous code.
  • Is a coroutine a threading framework?

    • The essence of coroutines is return +callback at compile time. It simply provides a scheduler that can run on an IO thread when scheduling tasks.
  • When to use coroutines

    • Multi-task concurrent flow control scenario is better to use, the flow control is relatively simple, does not involve thread blocking and wake up, the performance is higher than Java concurrency control means.