Article Directory
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 delay
that the function has a suspend
keyword, 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.INSTANCE
or 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.
- 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 - Because the state is 0 when it is created for the first time
label
, it will entercase 0:
the logic, executedelay
the function, and judge whether the return value isIntrinsicsKt.getCOROUTINE_SUSPENDED()
the same, that is, whether it is suspended, and pass the $continuation anonymous inner class object intodelay
the function. The label state is changed to 1 . $continuation
The anonymous inner class object has aninvokeSuspend
abstract method. When the coroutine wants to resume from the suspended state, it has to call thisinvokeSuspend
, it will callCompanion.this.test(this)
, and execute the logic inside the method again . Since the anonymous inner class objecttest
has been created for the first time , and the label has been$continuation
If it is modified to 1, it will directly entercase 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?
Continuation
actually 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: context
and resumeWith
functions.
- 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. - 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 suspend
decorated function will be converted into a function Callback
with . Here Callback
is Continuation
the 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:
- The suspend function needs to be used in the coroutine
- Suspend functions need to be used in other suspend functions
The above two points are actually Continuation
related to the above two points. As you can see by compiling it into Java code above, there will be an Continuation
object callback passed in, and this Continuation
needs 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
suspendCoroutine
The function of and suspendCancellableCoroutine
is 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 suspendCoroutine
convert 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 resume
the data of successful request is returned, and resumeWithException
the abnormal data is returned.
resume
and resumeWithException
both call resumeWith
the method and encapsulate Result
the object
resume
& resumeWithException
method 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
suspendCancellableCoroutine
The use and suspendCoroutine
usage of are basically the same, the difference is that the coroutine suspendCancellableCoroutine
can 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, cancel
the coroutine can be canceled by calling it. If you use the previous one suspendCoroutine
, you can't achieve this function because suspendCancellableCoroutine
an object is provided in the block body CancellableContinuation
.
suspendCancellableCoroutine
source 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>) -> Unit
representing CancellableContinuation
the type of its incoming parameter, which can call its own cancel
method.
Summarize
The suspend function has more suspend keywords than ordinary functions, and the Kotlin compiler will treat it specially. Convert the function into a Callback
function ( Continuation
interface) with , which will return if pending Intrinsics.COROUTINE_SUSPENDED
, otherwise, the result will be returned directly. The difference between suspendCoroutine
and is that it can be dropped.suspendCancellableCoroutine
suspendCancellableCoroutine
cancel