Android14
Android14

Reputation: 1125

Does Kotlin suspend funtion runs on a separate thread?

suspend funtions run on a seperate thread ? If not, then what is the performance benefit ?

suspend fun requestToken():Token {..}  // takes 2 sec to complete
suspend fun createPost (token:Token){..} // takes 3 sec to complete

 suspend fun postItem() {
    val token = requestToken()
    val post =createPost(token)
    processPost(post)
  }

So, when we reach at processPost(post) and if suspend function do not run on a seperate thread then we have to wait for requestToken() and createPost(token) method to complete (i.e 2+3= 5 seconds). As per the author, suspend is asyncronous,but if we are not spawning any new thread then how are we achieving asychronous behaviour ?

Upvotes: 4

Views: 2688

Answers (3)

Aleksei Mulin
Aleksei Mulin

Reputation: 355

Background single thread or multiple background threads of a thread pool can be explicitly declared and then used, for example by passing it as a parameter, let's call this parameter "scheduler". The really cool thing about it is that initially started from the main thread it's automatically switched to the scheduler thread to execute a particular task on it and virtual machine kind of suspended or interrupted at this place and the thing which is even cooler the main thread gets unblocked and can execute something else while there is the task in background.

As soon as the task is finished, virtual machine sort of gets back to the point where it was suspended or interrupted before and then it resumes its execution from that point but now by also having the result returned from the background thread of the scheduler, below is the code snippet:

private val backgroundThread = ThreadPoolExecutor(1, 1, 15L, TimeUnit.SECONDS, LinkedBlockingQueue())

GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT) {
    postItem(backgroundThread))
}

suspend fun CoroutineScope.postItem(scheduler: ThreadPoolExecutor): Boolean {
    val token = requestToken(scheduler)
    val post = createPost(token, scheduler)
    return processPost(post, scheduler)
}

private suspend fun CoroutineScope.requestToken(scheduler: ThreadPoolExecutor): String {
    val def: Deferred<String?> = async(scheduler.asCoroutineDispatcher(), CoroutineStart.DEFAULT) {
        val token = networkApi.requestToken()
    }

    return def.await() ?: ""
}

private suspend fun CoroutineScope.createPost(token: String, scheduler: ThreadPoolExecutor): String {
    val def: Deferred<String?> = async(scheduler.asCoroutineDispatcher(), CoroutineStart.DEFAULT) {
        val post = networkApi.createPost(token)
    }

    return def.await() ?: ""
}

private suspend fun CoroutineScope.processPost(post: String, scheduler: ThreadPoolExecutor): Boolean {
    val def: Deferred<Boolean?> = async(scheduler.asCoroutineDispatcher(), CoroutineStart.DEFAULT) {
        val result = networkApi.processPost(post)
    }

    return def.await() ?: false
}

Upvotes: -1

Minn
Minn

Reputation: 6124

Suspension-points can only be used within a coroutine context, for instance:

fun main() {
    delay(1000)
}

Would not work because delay is a suspending function and the compiler wouldn't know how to handle that without a coroutine. When you do have a coroutine it can use something called a dispatcher to control thread ownership. Suspending means that the thread is no longer used to execute that part of your program but its doing something else or going idle. The way it works is that you can have several coroutines working at the same time without having a thread for each, that thread can then execute parts of each coroutine up to a suspension point. Generally the idea is that you can view suspending functions as "generators" which have stages for producing a result.

suspend fun hello() {
    println("Hello")
    delay(1000) // suspend here, let someone else use the thread while we wait
    println("World")
    delay(1000) // suspend again, we can still use the thread while waiting
    println("done")
}

Everytime this is suspended it uses the thread to work on another function until that suspends and the delay expires, at that point this function will eventually resume execution up to the next suspension point or finish execution entirely.

This is different to blocking code as it does not waste the thread by putting it into wait state but rather borrows it to another function. That way no other threads need to be created to have concurrency as you can still work on multiple functions without true parallelism, instead it uses concurrent execution of parts of the functions.

Coroutines don't necessarily protect you from blocking, if you call Thread.sleep(1000) its still gonna be a blocking call. It is the responsibility of the programmer to use suspending equivalents to blocking functions in order to maximize effectiveness of this concept.

For more details, please read the documentation as its very detailed and helpful.

Upvotes: 1

Marko Topolnik
Marko Topolnik

Reputation: 200138

suspend is asynchronous

suspend funs execute synchronously with their caller. What you actually meant to say is "non-blocking" and that's a completely different story.

but if we are not spawning any new thread then how are we achieving asynchronous behaviour?

You are making the tacit assumption that all I/O must be blocking at some level. This is wrong. Non-blocking I/O works by pushing data to a send buffer and receiving notifications when there's data in the receive buffer. A suspend fun hooks into this mechanism by suspending itself after pushing the data to a send buffer and installing a callback that will resume it when response data is ready in the receive buffer.

Upvotes: 5

Related Questions