What does the suspend function mean in a Kotlin Coroutine?
Asked Answered
C

10

304

I'm reading Kotlin Coroutine and know that it is based on suspend function. But what does suspend mean?

Can Coroutine or function get suspended?

From https://kotlinlang.org/docs/reference/coroutines.html

Basically, coroutines are computations that can be suspended without blocking a thread

I heard people often say "suspend function". But I think it is the coroutine that gets suspended because it is waiting for the function to get finished? "suspend" usually means "cease operation", in this case, the coroutine is idle.

Should we say the coroutine is suspended?

Which coroutine gets suspended?

From https://kotlinlang.org/docs/reference/coroutines.html

To continue the analogy, await() can be a suspending function (hence also callable from within an async {} block) that suspends a coroutine until some computation is done and returns its result:

async { // Here I call it the outer async coroutine
    ...
    // Here I call computation the inner coroutine
    val result = computation.await()
    ...
}

It says "that suspends a coroutine until some computation is done", but coroutine is like a lightweight thread. So if the coroutine is suspended, how can the computation be done?

We see await is called on computation, so it might be async that returns Deferred, which means it can start another coroutine

fun computation(): Deferred<Boolean> {
    return async {
        true
    }
}

In the quote, it is mentioned that suspends a coroutine. Does it mean suspend the outer async coroutine, or suspend the inner computation coroutine?

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?

The reason I mention the thread is because of: https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html

The thread is returned to the pool while the coroutine is waiting, and when the waiting is done, the coroutine resumes on a free thread in the pool

Coworker answered 18/12, 2017 at 15:47 Comment(1)
suggest studying Basics Of Kotlin where this is explained in detailShanahan
B
334

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<T>. 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<T> 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)
}
Booker answered 22/10, 2018 at 8:23 Comment(8)
I wonder how is this function actually paused? They always say that suspend fun can be paused but how exactly?Leyden
@Leyden It just means that the current thread starts executing some other coroutine, and will come back to this one later.Carpet
I've figured out the "mysterious" mechanism. It can be easily unveiled with the help of Tools > Kotlin > Bytecode > Decompile btn. It shows how the so called "suspension point" is implemented - via Continuation and so on. Anyone can take a look for yourselves.Leyden
@buzaa Here's a talk from 2017 by Roman Elizarov that explains it at the bytecode level.Mason
On which thread does "along running operation" run? On the same thread that calls the suspend function?Laud
By this explanation it sounds like you could put anything inside long running operation and the thread will just pause the execution anywhere it sees fit. That doesn't sound accurate. Calling a blocking function from inside a suspend function will still block the thread it's running on. The suspend function will run, and if it finds another suspend function call, it will save its state, and free the thread for the next task in the call stack to be run. When that task is complete, the original function resumes. Anyway, that's just how I've understood it: youtu.be/BOHK_w09pVA?t=577Shrader
basically, in other languages it's "async" ... just another fancy nameEnact
@NextDeveloper "async" would imply asynchronous behaviour. With suspend functions the code still behaves synchronously from the point of view of the reader: a call to a suspend function has to complete before the next instruction is run. Calling an async function without await in JS returns a Promise, so it's actually asynchronous from the caller's point of view. The actual implementation is kinda irrelevant to the message that the naming wants to convey. See medium.com/@joffrey.bion/…Carpet
C
134

But what does suspend mean?

Functions marked with the suspend keyword are transformed at compile time to be made asynchronous under the hood (in bytecode), even though they appear synchronous in the source code.

The best source to understand this transformation IMO is the talk "Deep Dive into Coroutines" by Roman Elizarov.

For example, this function:

class MyClass {
    suspend fun myFunction(arg: Int): String {
        delay(100)
        return "bob"
    }
}

is turned into (expressed in Java instead of actual JVM bytecode for simplicity):

public final class MyClass {
    public final Object myFunction(int arg, @NotNull Continuation<? super String> $completion) {
        // body turned into a state machine, hidden for brevity
    }
}

This includes the following changes to the function:

  • The return type is changed to Java's Object (the equivalent of Kotlin's Any? - a type containing all values), to allow returning a special COROUTINE_SUSPENDED token to represent when the coroutine is actually suspended
  • It gets an additional Continuation<X> argument (where X is the former return type of the function that was declared in the code - in the example it's String). This continuation acts like a callback when resuming the suspend function.
  • Its body is turned into a state machine (instead of literally using callbacks, for efficiency). This is done by breaking down the body of the function into parts around so called suspension points, and turning those parts into the branches of a big switch. The state about the local variables and where we are in the switch is stored inside the Continuation object.

This is a very quick way to describe it, but you can see it happen with more details and with examples in the talk. This whole transformation is basically how the "suspend/resume" mechanism is implemented under the hood.

Coroutine or function gets suspended?

At a high level, we say that 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.

In fact, call sites of suspending functions are called "suspension points" for this reason.

Which coroutine gets suspended?

Let's look at your code and break down what happens (the numbering follows the execution timeline):

// 1. this call starts a new coroutine (let's call it C1).
//    If there were code after it, it would be executed concurrently with
//    the body of this async
async {
    ...
    // 2. this is a regular function call, so we go to computation()'s body
    val deferred = computation()
    // 4. we're back from the call to computation, about to call await()
    //    Because await() is suspendING, it suspends coroutine C1.
    //    This means that if we had a single thread in our dispatcher, 
    //    it would now be free to go execute C2. With multiple threads,
    //    C2 may have already started executing. In any case we wait 
    //    here for C2 to complete.
    // 7. once C2 completes, C1 is resumed with the result `true` of C2's async
    val result = deferred.await() 
    ...
    // 8. C1 can now keep going in the current thread until it gets 
    //    suspended again (or not)
}

fun computation(): Deferred<Boolean> {
    // 3. this async call starts a second coroutine (C2). Depending on the 
    //    dispatcher you're using, you may have one or more threads.
    // 3.a. If you have multiple threads, the block of this async could be
    //      executed in parallel of C1 in another thread
    // 3.b. If you have only one thread, the block is sort of "queued" but 
    //      not executed right away (as in an event loop)
    //
    //    In both cases, we say that this block executes "concurrently"
    //    with C1, and computation() immediately returns the Deferred
    //    instance to its caller (unless a special dispatcher or 
    //    coroutine start argument is used, but let's keep it simple).
    return async {
        // 5. this may now be executed
        true
        // 6. C2 is now completed, so the thread can go back to executing 
        //    another coroutine (e.g. C1 here)
    }
}

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.

The way this is actually achieved is by turning every suspending function into a state machine, where each "state" corresponds to a suspension point inside this suspend function. Under the hood, the function can be called multiple times, with the information about which suspension point it should start executing from (you should really watch the video I linked for more info about that).

Carpet answered 6/2, 2019 at 19:47 Comment(11)
Great answer, I miss that kind of really basic explanation when it comes to coroutines.Abdication
Why is not that implemented in any other language? Or am I missing something? I am thinking about that solution for so long, glad Kotlin has it, but not sure why TS or Rust have something like thatPointillism
@Pointillism well coroutines have been around for a long time. Kotlin didn't invent them, but the syntax and library make them shine. Go has goroutines, JavaScript and TypeScript have promises. The only difference is in the details of syntax to use them. I find it quite annoying/disturbing for JS's async functions to be marked this way and yet still return a Promise.Carpet
Sorry, my comment was not clear. I am referring to the suspend keyword. It is not the same as async.Pointillism
Good explanation. my understanding is - In the case of multithreaded/multicore machine, Coroutines may run in parallel on other thread or concurrently on the same thread and dispatching point would be the line calling the suspending function and the suspending point would be the line of await(). Is my understanding correct @Joffrey? and and any idea on how it decides to start another thread or continue on the same thread and whats the cost of new threads and how it optimized to reduce the context switching for a code with a lot of long running coroutines?Spinel
@Siva S Correct. The way coroutines are dispatched between threads is decided by the coroutine dispatcher, which can be customized. The dispatcher is one element of the Coroutine context, so you can switch to a different dispatcher at any point using withContext. You can also pass a dispatcher when calling coroutine builders such as launch or async. On the JVM, common dispatchers are Dispatchers.Default and Dispatchers.IO (look them up to learn how they behave). You can also create a custom dispatcher, for instance by creating an ExecutorService and calling asDispatcher()Carpet
This should be the accepted answer. It directly answers the two questions from the original post.Chaetognath
"""In fact, call sites of suspending functions are called "suspension points" for this reason""" - Not sure if it's correct. Create a suspend function A that simply calls another suspend function B in an endless loop. The function B does not do anything suspending, juest does a println() for instance. Now if you cancel a coroutine that currently runs function A, the endless loop will not stop. It will continue indefinitely. From which I conclude that the coroutine never reaches a suspending point which means a suspend function call site (A->B) is NOT a suspension point.Fascist
@GenaBatsyan Not all suspend functions are cancellable, so this test doesn't show that there is no suspension point, but rather that a suspend function with just a print statement is not cancellable. I tried decompiling the code you described, and the function A does have a state machine with a suspension point for the call to function B.Carpet
That being said, I'm surprised because I would have expected the call to function B to throw CancellationException when resuming.Carpet
After further investigation, let me clarify. To be more accurate, there is a suspension point in function A in the sense that the function is broken down into parts before and after the call, driven by a state machine, and can handle the COROUTINE_SUSPENDED value. BUT, this specific function B never actually suspends (it never returns COROUTINE_SUSPENDED), so the function A never suspends either (because it would need to receive that value), so the dispatcher never has the opportunity to stop executing the coroutine at that point.Carpet
M
52

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.GlobalScope
import kotlinx.coroutines.launch
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

var continuation: Continuation<Int>? = null

fun main() {
    GlobalScope.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.

Mason answered 5/1, 2018 at 11:42 Comment(2)
Dispatchers other than the unconfined one are basically returning immediately from resume(), pretty much.Julianjuliana
Very good example and explanation!Stampede
B
43

As many good answers are already there, I would like to post a simpler example for others.

runBlocking use case :

  • myMethod() is suspend function
  • runBlocking { } starts a Coroutine in blocking way. It is similar to how we were blocking normal threads with Thread class and notifying blocked threads after certain events.
  • runBlocking { } does block the current executing thread, until the coroutine (body between {}) gets completed

     override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        Log.i(TAG,"Outer code started on Thread : " + Thread.currentThread().name);
        runBlocking {
            Log.d(TAG,"Inner code started  on Thread : " + Thread.currentThread().name + " making outer code suspend");
            myMethod();
        }
        Log.i(TAG,"Outer code resumed on Thread : " + Thread.currentThread().name);
    }
    
    private suspend fun myMethod() {
        withContext(Dispatchers.Default) {
        for(i in 1..5) {
            Log.d(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
        }
    }
    

This outputs :

I/TAG: Outer code started on Thread : main
D/TAG: Inner code started  on Thread : main making outer code suspend
// ---- main thread blocked here, it will wait until coroutine gets completed ----
D/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-2
// ---- main thread resumes as coroutine is completed ----
I/TAG: Outer code resumed on Thread : main

launch use case :

  • launch { } starts a coroutine concurrently.
  • This means that when we specify launch, a coroutine starts execution on worker thread.
  • The worker thread and outer thread (from which we called launch { }) both runs concurrently. Internally, JVM may perform Preemptive Threading
  • When we require multiple tasks to run in parallel, we can use this. There are scopes which specify lifetime of coroutine. If we specify GlobalScope, the coroutine will work until application lifetime ends.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        Log.i(TAG,"Outer code started on Thread : " + Thread.currentThread().name);
    
        GlobalScope.launch(Dispatchers.Default) {
            Log.d(TAG,"Inner code started  on Thread : " + Thread.currentThread().name + " making outer code suspend");
            myMethod();
        }
        Log.i(TAG,"Outer code resumed on Thread : " + Thread.currentThread().name);
    }
    
    private suspend fun myMethod() {
        withContext(Dispatchers.Default) {
            for(i in 1..5) {
                Log.d(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
            }
        }
    }
    

This Outputs :

10806-10806/com.example.viewmodelapp I/TAG: Outer code started on Thread : main
10806-10806/com.example.viewmodelapp I/TAG: Outer code resumed on Thread : main
// ---- In this example, main had only 2 lines to execute. So, worker thread logs start only after main thread logs complete
// ---- In some cases, where main has more work to do, the worker thread logs get overlap with main thread logs
10806-10858/com.example.viewmodelapp D/TAG: Inner code started  on Thread : DefaultDispatcher-worker-1 making outer code suspend
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-1

async and await use case :

  • When we have multiple tasks to do and they depend on other's completion, async and await would help.
  • For example, in below code, there are 2 suspend functions myMethod() and myMethod2(). myMethod2() should get executed only after full completion of myMethod() OR myMethod2() depends on result of myMethod(), we can use async and await
  • async starts a coroutine in parallel similar to launch. But, it provides a way to wait for one coroutine before starting another coroutine in parallel.
  • That way is await(). async returns an instance of Deffered<T>. T would be Unit for default. When we need to wait for any async's completion, we need to call .await() on Deffered<T> instance of that async. Like in below example, we called innerAsync.await() which implies that the execution would get suspended until innerAsync gets completed. We can observe the same in output. The innerAsync gets completed first, which calls myMethod(). And then next async innerAsync2 starts, which calls myMethod2()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        Log.i(TAG,"Outer code started on Thread : " + Thread.currentThread().name);
    
         job = GlobalScope.launch(Dispatchers.Default) {
             innerAsync = async {
                 Log.d(TAG, "Inner code started  on Thread : " + Thread.currentThread().name + " making outer code suspend");
                 myMethod();
             }
             innerAsync.await()
    
             innerAsync2 = async {
                 Log.w(TAG, "Inner code started  on Thread : " + Thread.currentThread().name + " making outer code suspend");
                 myMethod2();
             }
        }
    
        Log.i(TAG,"Outer code resumed on Thread : " + Thread.currentThread().name);
        }
    
    private suspend fun myMethod() {
        withContext(Dispatchers.Default) {
            for(i in 1..5) {
                Log.d(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
            }
        }
    }
    
    private suspend fun myMethod2() {
        withContext(Dispatchers.Default) {
            for(i in 1..10) {
                Log.w(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
            }
        }
    }
    

This outputs :

11814-11814/? I/TAG: Outer code started on Thread : main
11814-11814/? I/TAG: Outer code resumed on Thread : main
11814-11845/? D/TAG: Inner code started  on Thread : DefaultDispatcher-worker-2 making outer code suspend
11814-11845/? D/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-2
// ---- Due to await() call, innerAsync2 will start only after innerAsync gets completed
11814-11848/? W/TAG: Inner code started  on Thread : DefaultDispatcher-worker-4 making outer code suspend
11814-11848/? W/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 6 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 7 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 8 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 9 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 10 on Thread : DefaultDispatcher-worker-4
Bruce answered 16/12, 2019 at 9:41 Comment(2)
Thanks for describing the use cases.Biforked
This is the most comprehensive and simple example to make someone new understand how coroutines and play of threads work! Thanks Kushal!Husky
D
15

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 coroutine can be paused by using a suspend function. lets investigate this:

In android we could do this for example :

var TAG = "myTAG:"
        fun myMethod() { // function A in image
            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() { // function B in image
            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")
                }
            }
        }

Above code 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:

enter image description here

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.

Let's 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 we'll 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, let's return to our suspended function and make it freeze in middle of 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 beeb passed. You can also look into channels

Dobb answered 5/6, 2019 at 10:43 Comment(0)
B
12

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.

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

Bering answered 4/7, 2019 at 7:48 Comment(0)
L
6

There are a lot of great answers here, but I think there are two additional things that are important to note.

launch / withContext / runBlocking and a lot of other things in the examples are from the coroutines library. which actually have nothing to do with suspend. you don't need the coroutines library to use coroutines. Coroutines are a compiler "trick". Yes, the library sure makes things easier, but the compiler is doing the magic of suspending & resuming things.

The second thing, is the compiler is just taking code that looks procedural and turning it into callbacks under the hood.

Take the following minimal coroutine that suspends that does not use the coroutine library :

lateinit var context: Continuation<Unit>

    suspend {
        val extra="extra"
        println("before suspend $extra")
        suspendCoroutine<Unit> { context = it }
        println("after suspend $extra")
    }.startCoroutine(
        object : Continuation<Unit> {
            override val context: CoroutineContext = EmptyCoroutineContext
            // called when a coroutine ends. do nothing.
            override fun resumeWith(result: Result<Unit>) {
                result.onFailure { ex : Throwable -> throw ex }
            }
        }
    )

    println("kick it")
    context.resume(Unit)

I think an important way to understand it is to look at what the compiler does with this code. effectively it creates a class for the lambda. it creates a property in the class for the "extra" string, then it creates two function, one that prints the "before" and another the prints the "after".

Effectively the compiler took what looks like procedural code and turned it into callbacks.

So what does the suspend keyword do? It tell the compiler how far back to look for context that the generated callbacks will need. The compiler needs to know which variables are used in which "callbacks", and the suspend keyword helps it. In this example the "extra" variable is used both before and after the suspend. So it needs to be pulled out to a property of the class containing the callbacks the compiler makes.

It also tells the compiler that this is the "beginning" of state and to prepare to split up the following code into callbacks. The startCoroutine only exists on suspend lambda.

The actual Java code generated by the Kotlin compiler is here. It's a switch statement instead of callbacks, but it's effectively the same thing. Called first w/ case 0, then w/ case 1 after the resume.

            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
                var10_2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
                switch (this.label) {
                    case 0: {
                        ResultKt.throwOnFailure((Object)$result);
                        extra = "extra";
                        var3_4 = "before delay " + extra;
                        var4_9 = false;
                        System.out.println((Object)var3_4);
                        var3_5 = this;
                        var4_9 = false;
                        var5_10 = false;
                        this.L$0 = extra;
                        this.L$1 = var3_5;
                        this.label = 1;
                        var5_11 = var3_5;
                        var6_12 = false;
                        var7_13 = new SafeContinuation(IntrinsicsKt.intercepted((Continuation)var5_11));
                        it = (Continuation)var7_13;
                        $i$a$-suspendCoroutine-AppKt$main$1$1 = false;
                        this.$context.element = it;
                        v0 = var7_13.getOrThrow();
                        if (v0 == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
                            DebugProbesKt.probeCoroutineSuspended((Continuation)var3_5);
                        }
                        v1 = v0;
                        if (v0 == var10_2) {
                            return var10_2;
                        }
                        ** GOTO lbl33
                    }
                    case 1: {
                        var3_6 = this.L$1;
                        extra = (String)this.L$0;
                        ResultKt.throwOnFailure((Object)$result);
                        v1 = $result;
lbl33:
                        // 2 sources

                        var3_8 = "after suspend " + extra;
                        var4_9 = false;
                        System.out.println((Object)var3_8);
                        return Unit.INSTANCE;
                    }
                }
                throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }
Lubricator answered 3/10, 2020 at 6:25 Comment(1)
that is a great explanationConsultant
V
5

In Kotlin Coroutines, the suspend function is a keyword used to mark a function as a coroutine. A coroutine is a lightweight thread-like structure that allows for the sequential execution of asynchronous or long-running operations without blocking the main thread.

When a function is marked with suspend, it means that the function can suspend its execution at certain points without blocking the thread, allowing other coroutines to run. These suspensions typically occur when waiting for asynchronous operations to complete, such as network requests or disk I/O. Here's an example of a function using the suspend keyword:

suspend fun fetchData(): String {
    delay(1000) // Simulating a delay for demonstration purposes [it also suspend function]
    return "Data fetched!"
}

what suspend function do ?

1- pause/resume 

2- save all variables (save the progress of function when function is pause and resume)

what you need to call suspend function ?

you need a coroutines scope to call suspend function or another suspend function 

type couroutines scope

1 - lifecycleScope -> (with activity) = aware activity lifecycle

2 - viewModelScope -> (with viewModel) = aware viewModel lifecycle

3 - GlobalScope -> will work with application scope

4 - runBlocking -> (not recommended because make blocking thread)

5 - more...

So who can i call suspend function using kotlin ? you call launch it in the 5 thread

1 - Main Thread -> using for UI

2 - IO Thread -> using for Input output

3- Deafult Thread

4 - Unconfined Thread

**5 - custom Thread -> you created it **

to call suspend function 3 things

first you need `builder` (Scope)
second `Dispatchers` (for any thread you will work) [Optional]
third `body of scope` you can call suspend here

let's take an example

fun main(){
   GlobalScope.launch(Dispatchers.IO/*[Optional]*/){
      // this is body 
      for(i in 1..10){
        Log.i("TEST", fetchData())   // open logcat to see the result           
      }
   }
}

when you finish the coroutine will go back to Main thread

Verbatim answered 31/5, 2023 at 14:51 Comment(0)
R
2

Let's say we have a function named myFunction.

fun myFunction(){
Code block 1
Code block 2 //this one has a long running operation
Code block 3
Code block 4
}

Usually these code blocks execute like block1, block2, block3, block4 . So code block 3 and 4 might execute while code block 2 is still running. Because of that reason there can be problems. (screen might freeze, app might crash)

But if we make this function suspend

suspend fun MyFunction(){
Code block 1
Code block 2 //this one has a long running operation
Code block 3
Code block 4
}

Now, this function can get paused when code block 2(long running operation) starts executing and get resumed when it is done. Code block 3 and 4 will execute after that. So there will be no unexpected thread sharing issues.

Ravage answered 21/3, 2022 at 4:20 Comment(1)
How can blocks 3 and 4 get executed when block 2 is unfinished in the without-suspend function example?Wailful
C
-1

For anyone still wondering how do we actually suspend a suspend function, we use the suspendCoroutine function in the body of the suspend function .

    suspend fun foo() :Int
  {
    Log.d(TAG,"Starting suspension")
    return suspendCoroutine<Int> { num->

      val result = bar()
      Log.d(TAG,"Starting resumption")           
      num.resumeWith(Result.success(result))
    }

  }

fun bar():Int //this is a long runnning task
Cohobate answered 15/8, 2021 at 7:46 Comment(1)
I don't know what you were trying to achieve by this code, but suspendCoroutine is mainly used with some legacy code with callbacks. What is the point of using suspendCoroutine here? It doesn't switch context to background thread, so it will block the thread in which the coroutine runs. If the coroutine uses Dispatchers.Main context, it will block the Main Thread.Hobnailed

© 2022 - 2024 — McMap. All rights reserved.