Search code examples
kotlinkotlin-coroutinessuspend

Understanding the idea and usecases of suspendCoroutine


I am reading Kotlin Coroutines Deep Dive by Marcin Moskala, and I have a problem understanding suspendCoroutine function.

AFAIK, suspendCoroutine is a low-level API that helps you possess the continuation object before the coroutine suspends and do some action with it.

As Marcin Moskala states,

it ends with a lambda expression ({ }). The function passed as an argument will be invoked before the suspension. This function gets a continuation as an argument. The suspendCoroutine function makes it possible to use the continuation just before the suspension. After the suspendCoroutine call, it would be too late.

So, what's really happening here? I have the following questions:

  1. If we place cont.resume(result), does it resume before even suspending?
  2. There is a synchronous code inside lambda - some heavy operations, for example, network request. So how does it suspend? It should wait until the operation completion, and it doesn't suspend inside because all the tasks are synchronous. Even the callback-based API should be done synchronously. Am I wrong?
  3. Then how is it any different from directly using threads, except coroutines are lightweight, etc.?

I am aware of this answer, but still no understanding

I also know that technically this continuation object can be saved, coroutine indeed suspends, and later we can call .resume on saved object on some other control flow, but this scenario seems to be misuse of coroutines API due to memory leaks.

Is it just a wrapper on callbacks so as to avoid callback hell?

Please, clarify the idea of using suspendCoroutine.


Solution

  • suspendCoroutine is used to bridge other asynchronous models like callbacks, futures, etc. with coroutines. It "converts" existing asynchronous code to suspending.

    Generally speaking, the usage pattern is like this:

    1. Acquire the continuation and store it somewhere.
    2. Invoke the asynchronous operation (it runs in the background).
    3. Return from the lambda, then the coroutine suspends.
    4. At some point in the future, call the resume to resume the coroutine. How/where/when we call resume depends highly on the type of the asynchronous operation and its API.

    Answering your questions directly:

    Ad.1. If we put resume directly in the lambda (which could make sense in some cases), I suspect it won't even suspend. But it can also suspend and resume immediately. I think the function doesn't guarantee any of these behaviors.

    Ad.2. If we put synchronous, blocking code directly in the lambda, that means we blocked the coroutine, in a similar way as if we don't at all use suspendCoroutine. This is incorrect way of using suspendCoroutine. It is generally expected that the lambda passed to suspendCoroutine only schedules the asynchronous operation and finishes almost immediately.

    I also know that technically this continuation object can be saved, coroutine indeed suspends, and later we can call .resume on saved object on some other control flow, but this scenario seems to be misuse of coroutines API due to memory leaks.

    Is it just a wrapper on callbacks so as to avoid callback hell?

    This is not a misuse of coroutines API, this is exactly how we are supposed to use suspendCoroutine. It doesn't cause any memory leaks, assuming the original code based on callbacks didn't cause them.