What does suspend function mean in Kotlin Coroutine

╄→尐↘猪︶ㄣ 提交于 2019-11-27 18:42:47

Suspending functions are at the center of everything coroutines. A suspending function is simply a function that can be paused and resumed at a later time. They can execute a long running operation and wait for it to complete without blocking.

The syntax of a suspending function is similar to that of a regular function except for the addition of the suspend keyword. It can take a parameter and have a return type. However, suspending functions can only be invoked by another suspending function or within a coroutine.

suspend fun backgroundTask(param: Int): Int {
     // long running operation
}

Under the hood, suspend functions are converted by the compiler to another function without the suspend keyword, that takes an addition parameter of type Continuation . The function above for example, will be converted by the compiler to this:

fun backgroundTask(param: Int, callback: Continuation<Int>): Int {
   // long running operation
}

Continuation is an interface that contains two functions that are invoked to resume the coroutine with a return value or with an exception if an error had occurred while the function was suspended.

interface Continuation<in T> {
   val context: CoroutineContext
   fun resume(value: T)
   fun resumeWithException(exception: Throwable)
}
Marko Topolnik

To understand what exactly it means to suspend a coroutine, I suggest you go through this code:

import kotlinx.coroutines.Dispatchers.Unconfined
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

var continuation: Continuation<Int>? = null

fun main() = runBlocking {
    launch(Unconfined) {
        val a = a()
        println("Result is $a")
    }
    10.downTo(0).forEach {
        continuation!!.resume(it)
    }
}

suspend fun a(): Int {
    return b()
}

suspend fun b(): Int {
    while (true) {
        val i = suspendCoroutine<Int> { cont -> continuation = cont }
        if (i == 0) {
            return 0
        }
    }
}

The Unconfined coroutine dispatcher eliminates the magic of coroutine dispatching and allows us to focus directly on bare coroutines.

The code inside the launch block starts executing right away on the current thread, as a part of the launch call. What happens is as follows:

  1. Evaluate val a = a()
  2. This chains to b(), reaching suspendCoroutine.
  3. Function b() executes the block passed to suspendCoroutine and then returns a special COROUTINE_SUSPENDED value. This value is not observable through the Kotlin programming model, but that's what the compiled Java method does.
  4. Function a(), seeing this return value, itself also returns it.
  5. The launch block does the same and control now returns to the line after the launch invocation: 10.downTo(0)...

Note that, at this point, you have the same effect as if the code inside the launch block and your fun main code are executing concurrently. It just happens that all this is happening on a single native thread so the launch block is "suspended".

Now, inside the forEach looping code, the program reads the continuation that the b() function wrote and resumes it with the value of 10. resume() is implemented in such a way that it will be as if the suspendCoroutine call returned with the value you passed in. So you suddenly find yourself in the middle of executing b(). The value you passed to resume() gets assigned to i and checked against 0. If it's not zero, the while (true) loop goes on inside b(), again reaching suspendCoroutine, at which point your resume() call returns, and now you go through another looping step in forEach(). This goes on until finally you resume with 0, then the println statement runs and the program completes.

The above analysis should give you the important intuition that "suspending a coroutine" means returning the control back to the innermost launch invocation (or, more generally, coroutine builder). If a coroutine suspends again after resuming, the resume() call ends and control returns to the caller of resume().

The presence of a coroutine dispatcher makes this reasoning less clear-cut because most of them immediately submit your code to another thread. In that case the above story happens in that other thread, and the coroutine dispatcher also manages the continuation object so it can resume it when the return value is available.

Coroutine or function gets suspended?

Calling a suspending function suspends the coroutine, meaning the current thread can start executing another coroutine. So, the coroutine is said to be suspended rather than the function.

But technically, your function will not be executed by another coroutine at that point, so we can say that both the function and the coroutine stop, but we're splitting hairs here.

Which coroutine gets suspended?

The outer async starts a coroutine. When it calls computation(), the inner async starts a second coroutine. Then, the call to await() suspends the execution of the outer async coroutine, until the execution of the inner async's coroutine is over.

You can even see that with a single thread: the thread will execute the outer async's beginning, then call computation() and reach the inner async. At this point, the body of the inner async is skipped, and the thread continues executing the outer async until it reaches await(). await() is a "suspension point", because await is a suspending function. This means that the outer coroutine is suspended, and thus the thread starts executing the inner one. When it is done, it comes back to execute the end of the outer async.

Does suspend mean that while outer async coroutine is waiting (await) for the inner computation coroutine to finish, it (the outer async coroutine) idles (hence the name suspend) and returns thread to the thread pool, and when the child computation coroutine finishes, it (the outer async coroutine) wakes up, takes another thread from the pool and continues?

Yes, precisely.

I wanted to give you a simple example of the concept of continuation. This is what a suspend function does it can freeze/suspend and then it continues/resumes. Stop thinking of coroutine in terms of threads and Semaphore. Think of it in terms of continuation and even callback hooks.

To be clear, a courtine can be paused by using a suspect function. lets investigate this:

in android we could do this for example:

var TAG = "myTAG:"
        fun myMethod() {
            viewModelScope.launch(Dispatchers.Default) {
                for (i in 10..15) {
                    if (i == 10) { //on first iteration, we will completely FREEZE this coroutine (just for loop here gets 'suspended`)
                        println("$TAG im a tired coroutine - let someone else print the numbers async. i'll suspend until your done")
                        freezePleaseIAmDoingHeavyWork()
                    } else
                        println("$TAG $i")
                }
            }

            //this area is not suspended, you can continue doing work
        }


        suspend fun freezePleaseIAmDoingHeavyWork() {
            withContext(Dispatchers.Default) {
                async {
                    //pretend this is a big network call
                    for (i in 1..10) {
                        println("$TAG $i")
                        delay(1_000)//delay pauses coroutine, NOT the thread. use  Thread.sleep if you want to pause a thread. 
                    }
                    println("$TAG phwww finished printing those numbers async now im tired, thank you for freezing, you may resume")
                }
            }
        }

which prints the following:

I: myTAG: my coroutine is frozen but i can carry on to do other things

I: myTAG: im a tired coroutine - let someone else print the numbers async. i'll suspend until your done

I: myTAG: 1
I: myTAG: 2
I: myTAG: 3
I: myTAG: 4
I: myTAG: 5
I: myTAG: 6
I: myTAG: 7
I: myTAG: 8
I: myTAG: 9
I: myTAG: 10

I: myTAG: phwww finished printing those numbers async now im tired, thank you for freezing, you may resume

I: myTAG: 11
I: myTAG: 12
I: myTAG: 13
I: myTAG: 14
I: myTAG: 15

imagine it working like this:

so the current function you launched from does not stop, just a coroutine would suspend while it continues. the thread is not paused by running a suspend function.

i think this site can help you straight things out and is my reference.

lets do something cool and freeze our suspend function in the middle of an iteration. we will resume it later in onResume:

store a variable called continuation and well load it with the coroutines continuation object for us:

var continuation: CancellableContinuation<String>? = null

suspend fun freezeHere() = suspendCancellableCoroutine<String> {
            continuation = it
        }

 fun unFreeze(){
            continuation?.resume("im resuming") {}
        }

now lets return to our suspend function and make it freeze mid iteration:

 suspend fun freezePleaseIAmDoingHeavyWork() {
        withContext(Dispatchers.Default) {
            async {
                //pretend this is a big network call
                for (i in 1..10) {
                    println("$TAG $i")
                    delay(1_000)
                    if(i == 3)
                        freezeHere() //dead pause, do not go any further
                }

            }
        }
    }

then somewhere else like in onResume (for example):

override fun onResume() {
        super.onResume()
        unFreeze()
    }

and the loop will continue. its pretty neat to know we can freeze a suspend function at any point and resume it after some time has passed. you can also look into channels

I've found that the best way to understand suspend is to make an analogy between this keyword and coroutineContext property.

Kotlin functions can be declared as local or global. Local functions magically have access to this keyword while global don't.

Kotlin functions can be declared as suspend or blocking. suspend functions magically have access to coroutineContext property while blocking functions don't.

The thing is: coroutineContext property is declared like a "normal" property in Kotlin stdlib but this declaration is just a stub for documentation/navigation purposes. In fact coroutineContext is builtin intrinsic property that means under the hood compiler magic aware of this property like it aware of language keywords.

What this keyword does for local functions is what coroutineContext property does for suspend functions: it gives access to current context of execution - to class instance context in first case and to coroutine instance context in second case.

So, you need suspend to get an access to coroutineContext property - the instance of currently executed coroutine context

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!