22

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)
}
Sergio
  • 27,326
  • 8
  • 128
  • 149
bernardo.g
  • 826
  • 1
  • 12
  • 27
  • Why do you want to wrap it around the `suspendCoroutine?` – Animesh Sahu Jul 01 '20 at 03:10
  • 1
    `suspendCoroutine` is a low level wrapper that you really should only use to wrap non-coroutine asynchronous APIs. There's no reason you should need to call a suspend function from it. – Tenfour04 Jul 01 '20 at 03:19
  • The `suspendCoroutine` call wraps a callback-based api call. The `mySuspendingFunction` call is inside one of the callbacks. – bernardo.g Jul 01 '20 at 11:30

2 Answers2

18

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"
}
Sergio
  • 27,326
  • 8
  • 128
  • 149
  • 1
    Yeah, the `suspendCoroutine` call is wrapping a callback-based api call. The call to `mySuspendingFunction` is inside one of the callbacks. I imagine calling `CoroutineScope().launch{}` inside `suspendCoroutine` would be bad, right? – bernardo.g Jul 01 '20 at 11:52
  • I guess it would be not a good idea. Why don't you call `mySuspendingFunction` before or after `someFunction`? – Sergio Jul 01 '20 at 13:11
  • I'll do that. Thanks for the help! – bernardo.g Jul 01 '20 at 17:52
  • 1
    I have exactly the same issue. And I got a very good reason why I cannot reorder the executions. I want to build a suspend function for Kafka's Producer.send(data, callback) call. This all looked nice and worked - kind of. It turns out, the send method Kafka provides is both *async* and *blocking* at the same time. Awkward design! So I had the idea to call it using runInterruptible { producer.send(data, callback) } - but I can ONLY to this *in* the suspendCancellableCoroutine because to build the callback, I do need the continuation. Cannot do it before... :-( This is such a bad situation... – Zordid May 23 '21 at 17:54
3

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()
}
Maartyl
  • 66
  • 4