Kotlin Coroutine Learning Road (2): Suspend Function

foreword

The suspend function is very important in the coroutine, and it is very important to understand the suspend function correctly.

1. Suspend function

The most common suspending function in coroutines is the delay function. It is also very simple to use:

runBlocking {
    
    
   delay(1000)
   // 一秒之后再执行"test end!"
   println("test end !")
}

Let's look at the function first delay:

// CancellableContinuation是Continuation子接口
public suspend fun delay(timeMillis: Long) {
    
    
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@ {
    
     cont: CancellableContinuation<Unit> ->
        // if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
        if (timeMillis < Long.MAX_VALUE) {
    
    
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        }
    }
}

You can see delaythat the function has a suspendkeyword, the English translation has the meaning of "pause; abort", and its main function is to identify that the function is a suspending function.

1.1 The principle of the suspend function

Give an example to illustrate:

// 自定义一个带返回值suspend函数
private suspend fun test0(): String{
    
    
      return "data"
}

// 自定义一个不带返回值suspend函数
private suspend fun test1(){
    
    
      // Empty Code
}

Then compile the above suspend function into java code:

// 这两个函数的Continuation对象由Kotlin编译器传入

// test0函数的反编译
private final Object test0(Continuation $completion) {
    
    
      return "data";
}

// test1函数的反编译
private final Object test1(Continuation $completion) {
    
    
      return Unit.INSTANCE;
}

If you add the delay function to the suspend function

private suspend fun test(){
    
    
     delay(1_000)
}

Compile to Java code:

private final Object test(Continuation $completion) {
    
    
       Object var10000 = DelayKt.delay(1000L, $completion);
       return var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED() ? var10000 : Unit.INSTANCE;
}

If the return value is Intrinsics.COROUTINE_SUSPENDED, it means that the function is suspended. If it is just an ordinary function, return directly Unit.INSTANCEor return the result. (similar to the first example)

More complex example: return value + delay function:

private suspend fun test(): String{
    
    
      delay(1_000)
      return "data"
}

Quite complex decompiled code, some comments are given.
Compile to Java code:

private final Object test(Continuation var1) {
    
    
         Object $continuation;
         label20: {
    
    
         	// 不是第一次进入,传入的Continuation是$continuation匿名内部类类型,就强转下
            if (var1 instanceof <undefinedtype>) {
    
    
               $continuation = (<undefinedtype>)var1;
               if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
    
    
                  ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
                  break label20;
               }
            }
			
			// 如果是第一次进入就以匿名内部类的形式创建
			// ContinuationImpl是一个抽象类,implement了Continuation接口
            $continuation = new ContinuationImpl(var1) {
    
    
               // $FF: synthetic field
               Object result;
               int label; // 初始值为0

               @Nullable
               public final Object invokeSuspend(@NotNull Object $result) {
    
    
                  this.result = $result;
                  this.label |= Integer.MIN_VALUE;
                  //开启协程状态机
                  return Companion.this.test(this);
               }
            };
         }
		
		 // 获取result执行结果
         Object $result = ((<undefinedtype>)$continuation).result;
         // 挂起的标志,如果挂起的话,就返回这个flag
         Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
         switch (((<undefinedtype>)$continuation).label) {
    
    
            case 0:
            	// 检测异常
               ResultKt.throwOnFailure($result);
               //将label的状态改成1,方便待会儿执行delay后面的代码
               ((<undefinedtype>)$continuation).label = 1;
//1. DelayKt.delay是一个挂起函数,正常情况下,它会立马返回一个值:IntrinsicsKt.COROUTINE_SUSPENDED(也就是这里的flag),表示该函数已被挂起,这里就直接return了,该函数被挂起
//2. 恢复执行:在DelayKt.delay内部,到了指定的时间后就会调用$continuation这个Callback的invokeSuspend(也就是上面匿名函数实现的方法)
//3. invokeSuspend中又将执行test函数,同时将之前创建好的$continuation传入其中,开始执行后面的逻辑(label为1的逻辑),该函数继续往后面执行(也就是恢复执行)
               if (DelayKt.delay(1000L, (Continuation)$continuation) == var4) {
    
    
                  return var4;
               }
               break;
            case 1:
            	// 检测异常
            	//label 1这里没有return,而是会走到下面的return "data"语句
               ResultKt.throwOnFailure($result);
               break;
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
         }

         return "data";
      }

Through the above analysis, we can understand the suspend and resume logic of the suspend function.

  1. When the test function is called for the first time, an anonymous inner class of ContinuationImpl will be created in the test function, which contains label (the current state of the coroutine state machine) and result (save the return result of the invokeSuspend callback), and the label at this time$continuation is 0
  2. Because the state is 0 when it is created for the first time label, it will enter case 0:the logic, execute delaythe function, and judge whether the return value is IntrinsicsKt.getCOROUTINE_SUSPENDED()the same, that is, whether it is suspended, and pass the $continuation anonymous inner class object into delaythe function. The label state is changed to 1 .
  3. $continuationThe anonymous inner class object has an invokeSuspendabstract method. When the coroutine wants to resume from the suspended state, it has to call this invokeSuspend, it will call Companion.this.test(this), and execute the logic inside the method again . Since the anonymous inner class object testhas been created for the first time , and the label has been $continuationIf it is modified to 1, it will directly enter case 1:the logic, and after detecting the abnormality, it will directlyreturn "data"

The above is a brief logical analysis of the suspend and resume of the suspend function.

1.2 Suspend and Continuation

Objects will appear in the above code Continuation, so what is this object?

Continuationactually an interface

@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>)
}

This interface mainly consists of two parts: contextand resumeWithfunctions.

  1. context : coroutine context, the main function is to save information, it is essentially a special collection, there is a key corresponding to an element. Due to the internal operator overloading, you can directly use +the combination element, which will be introduced in the following articles.
  2. resumeWith : As you can see from the comments, the essence is a method of passing a success or failure.

By compiling into Java code, the suspenddecorated function will be converted into a function Callbackwith . Here Callbackis Continuationthe object, this conversion is the so-called CPS conversion .

When we use the suspend modified suspend function, we need to pay attention to two points:

  1. The suspend function needs to be used in the coroutine
  2. Suspend functions need to be used in other suspend functions

The above two points are actually Continuationrelated to the above two points. As you can see by compiling it into Java code above, there will be an Continuationobject callback passed in, and this Continuationneeds to be obtained in the coroutine or other suspending functions, so it leads to Must be used in coroutines or in other suspending functions.

二.suspendCoroutine和suspendCancellableCoroutine

suspendCoroutineThe function of and suspendCancellableCoroutineis that kotlin converts the callback function into a suspend function. Intuitively, the asynchronous call form can be changed to a synchronous call form.

2.1 suspendCoroutine

Take a chestnut:

// NetWork类
class NetWork {
    
    

	// 回调接口
    interface CallBack{
    
    
		// 回调成功
        fun onSuccess(data: String)

		// 回调失败
        fun onError(e: Exception)
    }

    companion object{
    
    
    
    	// 经典调用
        fun request(callBack: CallBack?){
    
    
            thread {
    
    
            	// 模拟耗时
                Thread.sleep(1_000 * 2)
                if (Random.nextBoolean()){
    
    
                    // 模拟获取数据
                    callBack?.onSuccess("data")
                }else{
    
    
                    // 模拟网络异常
                    callBack?.onError(Exception("网络异常!"))
                }
            }
        }
    }
}
fun main() {
    
    
	
	// 调用getData函数
    NetWork.request(object :NetWork.CallBack{
    
    
    
          override fun onSuccess(data: String) {
    
    
               println("get Data:: $data")
           }

          override fun onError(e: Exception) {
    
    
               println("e= ${
      
      e.message}")
          }
    })
}

The above writing method is very common in Java, and now we suspendCoroutineconvert it into a suspending function.

// NetWork
class NetWork {
    
    

    interface CallBack{
    
    

        fun onSuccess(data: String)

        fun onError(e: Exception)
    }

    companion object{
    
    
    
    	// 经典调用
        private fun request(callBack: CallBack?){
    
    
            thread {
    
    
            	// 模拟耗时
                Thread.sleep(1_000 * 2)
                if (Random.nextBoolean()){
    
    
                    // 模拟获取数据
                    callBack?.onSuccess("data")
                }else{
    
    
                    // 模拟网络异常
                    callBack?.onError(Exception("网络异常!"))
                }
            }
        }

		// 形式上直接获取数据
        suspend fun requestDefault(): String{
    
    
            return suspendCoroutine {
    
    
                request(object :CallBack{
    
    
                    override fun onSuccess(data: String) {
    
    
                        it.resume(data)
                    }

                    override fun onError(e: Exception) {
    
    
                        it.resumeWithException(e)
                    }
                })
            }
        }
    }
}
fun main() {
    
    
    runBlocking {
    
    
      println(NetWork.requestDefault())
    }
}

Output result:

test

or

Exception in thread "main" java.lang.Exception: 网络异常!
	at com.work.kotlin.practice.NetWork$Companion$request$1.invoke(NetWork.kt:30)
	at com.work.kotlin.practice.NetWork$Companion$request$1.invoke(NetWork.kt:22)
	at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)

Process finished with exit code 1

It can be seen from the output result that resumethe data of successful request is returned, and resumeWithExceptionthe abnormal data is returned.
resumeand resumeWithExceptionboth call resumeWiththe method and encapsulate Resultthe object

resume& resumeWithExceptionmethod source code:

@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))

2.2 suspendCancellableCoroutine

suspendCancellableCoroutineThe use and suspendCoroutineusage of are basically the same, the difference is that the coroutine suspendCancellableCoroutinecan be manually canceled through cancel()the method .

class NetWork {
    
    

    interface CallBack{
    
    

        fun onSuccess(data: String)

        fun onError(e: Exception)
    }

    companion object{
    
    

        // 经典调用
        private fun request(callBack: CallBack?){
    
    
            thread {
    
    
                // 模拟耗时
                Thread.sleep(1_000 * 2)
                if (Random.nextBoolean()){
    
    
                    // 模拟获取数据
                    callBack?.onSuccess("data")
                }else{
    
    
                    // 模拟网络异常
                    callBack?.onError(Exception("网络异常!"))
                }
            }
        }

        suspend fun requestDefault(): String{
    
    
       		// 这里直接将suspendCoroutine替换为suspendCancellableCoroutine
            return suspendCancellableCoroutine {
    
    
            	it.invokeOnCancellation {
    
    
                    // 相当于调用cancel后的回调
                    println("invokeOnCancellation: cancel the request!")
                }
                request(object :CallBack{
    
    
                    override fun onSuccess(data: String) {
    
    
                        it.resume(data)
                    }

                    override fun onError(e: Exception) {
    
    
                        it.resumeWithException(e)
                    }
                })
            }
        }
    }
		fun main() {
    
    
            runBlocking {
    
    
             	// launch也是一种构建协程的方式,CoroutineScope的扩展函数
                val job = launch {
    
    
                    val data = NetWork.requestDefault()
                    println(data)
                }
                // 注意这里的delay时间小于上面request的sleep时间
                delay(100)
                // 协程取消
                job.cancel()
            }
        }

Output result:

invokeOnCancellation: cancel the request!

or

Exception in thread "main" java.lang.Exception: 网络异常!
	at com.work.kotlin.practice.NetWork$Companion$request$1.invoke(NetWork.kt:30)
	at com.work.kotlin.practice.NetWork$Companion$request$1.invoke(NetWork.kt:22)
	at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)

As can be seen from the above results, cancelthe coroutine can be canceled by calling it. If you use the previous one suspendCoroutine, you can't achieve this function because suspendCancellableCoroutinean object is provided in the block body CancellableContinuation.
suspendCancellableCoroutinesource code:

public suspend inline fun <T> suspendCancellableCoroutine(
    crossinline block: (CancellableContinuation<T>) -> Unit
): T =
    suspendCoroutineUninterceptedOrReturn {
    
     uCont ->
        val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
        /*
         * For non-atomic cancellation we setup parent-child relationship immediately
         * in case when `block` blocks the current thread (e.g. Rx2 with trampoline scheduler), but
         * properly supports cancellation.
         */
        cancellable.initCancellability()
        block(cancellable)
        cancellable.getResult()
    }

The type of the block variable is a variable (CancellableContinuation<T>) -> Unitrepresenting CancellableContinuationthe type of its incoming parameter, which can call its own cancelmethod.

Summarize

The suspend function has more suspend keywords than ordinary functions, and the Kotlin compiler will treat it specially. Convert the function into a Callbackfunction ( Continuationinterface) with , which will return if pending Intrinsics.COROUTINE_SUSPENDED, otherwise, the result will be returned directly. The difference between suspendCoroutineand is that it can be dropped.suspendCancellableCoroutinesuspendCancellableCoroutinecancel

Guess you like

Origin blog.csdn.net/RQ997832/article/details/128280581