Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the appropriate way of calling suspending functions inside a suspendCoroutine block?

I need to call a suspending function inside a suspendCoroutine block, before I call continuation.resume(). What is the appropriate way of doing that?

private suspend fun someFunction() = suspendCoroutine { cont ->
    //...
    val myResult = mySuspendingFunction() //<--- The IDE says "Suspension functions can be called only within coroutine body"
    cont.resume(myResult)
}
like image 279
bernardo.g Avatar asked Jul 01 '20 01:07

bernardo.g


People also ask

What are suspending functions?

The definition of suspend function: Suspend function is a function that could be started, paused, and resume.

Can we call Suspend function in main thread?

Suspend functions should be main-safe, meaning they're safe to call from the main thread. If a class is doing long-running blocking operations in a coroutine, it's in charge of moving the execution off the main thread using withContext .

What is the difference between suspending vs blocking?

Hunting to know BLOCKING vs SUSPENDINGA process is blocked when there is some external reason that it can not be restarted, e.g., an I/O device is unavailable, or a semaphore file is locked. A process is suspended means that the OS has stopped executing it, but that could just be for time-slicing (multitasking).

How does coroutine suspension work?

A coroutine is an instance of suspendable computation. It may suspend its execution in one thread and resume in another one. delay is a special suspending function. It suspends the coroutine for a specific time.


2 Answers

You can't call a suspend function in suspendCoroutine block, because it accepts non suspend block as parameter:

suspend inline fun <T> suspendCoroutine(
    crossinline block: (Continuation<T>) -> Unit
): T

'suspendCoroutine' mainly used when we have some legacy code with callbacks, e.g.:

suspend fun getUser(id: String): User = suspendCoroutine { continuation ->
      Api.getUser(id) { user ->
          continuation.resume(user)
      }
}

If function someFunction() doesn't call Api with callbacks then you should reconsider your approach getting rid of 'suspendCoroutine':

private suspend fun someFunction() {
    // ...
    val myResult = mySuspendingFunction()
    // ...
}

If you still want to use suspendCoroutine move call of mySuspendingFunction out of suspendCoroutine block:

private suspend fun someFunction(): String {
    val myResult = mySuspendingFunction()

    return suspendCoroutine { cont ->
        //...
        cont.resume(myResult)
    }
}

suspend fun mySuspendingFunction(): String {
    delay(1000) // simulate request
    return "result"
}
like image 84
Sergey Avatar answered Oct 23 '22 12:10

Sergey


It's best to avoid this and call the suspending function before suspendCoroutine, as others have answered. That is possible for the specific case in question.

However, that is not possible if you need the continuation.

(The following is for those, who found this question for the this reason, as @Zordid and I have. chan.send is an example of this.)

In which case, the following is a possible, but error prone way to do it, that I do not recommend:

suspend fun cont1() {
    //btw. for correct implementation, this should most likely be at least suspendCancellableCoroutine
    suspendCoroutine<Unit> { uCont ->
        val x = suspend { chan.send(foo(uCont)) }
        x.startCoroutine(Continuation(uCont.context) {
            if (it.isFailure)
                uCont.resumeWith(it)
            // else resumed by whatever reads from chan
        })
    }
}

(I think the error handling alone illustrates why it's not a great option, despite other problems.)

A better, safer and cheaper way is to use CompletableDeferred if you can.

If you must pass in a Continuation, it's still safer and probably cheaper to do:

suspend fun cont2() {
    val rslt = CompletableDeferred<Unit>()
    chan.send(foo(Continuation(currentCoroutineContext()) {
        rslt.completeWith(it)
    }))
    rslt.await()
}
like image 44
Maartyl Avatar answered Oct 23 '22 13:10

Maartyl