Misleading coroutine behavior

suspendCoroutine is a low-level primitive that is designed for conversion of callbacks into suspending functions. You should not use suspendCoroutine in your high-level code, precisely because invocation of suspendCoroutine exposes coroutine implementation details (a Continuation object) and can break the illusion of sequential execution in the way you’ve shown.

In particular, you should not mix your business-logic with suspendCoroutine invocations. Your high-level code should never work with Continuation objects directly. Continuation is exposed in Kotlin via suspendCoroutine only for the purpose of implementing low-level suspending functions.

Let us rewrite exec2 from your first example with this principle in mind:

suspend fun exec2() {
    println("exec2 started")
    exec2Cancel()
    Thread.sleep(1000)
    println("exec2 finished")
}

suspend fun exec2Cancel() = suspendCoroutine<Unit> { cont ->
    thread {
        println("exec2 canceled")
        cont.resumeWithException(Exception("oops"))
    }
}

Now exec2 does not use suspendCoroutine and it indeed executes sequentially just as it is supposed to. In particular, because exec2Cancel suspending function asynchronously completes with exception, the invocation of exec2 also completes with exception, never reaching “exec2 finished” line.

Because a code within a suspendCoroutine function can be another long-running Coroutine which can decide by itself whether to run its code in current or parallel thread

No. Code in suspendCoroutine cannot be a coroutine itself. The block of code that suspendCoroutine accepts is not a suspending function. Basically, the invocation of suspendCoroutine breaks out of coroutine into a regular non-suspending code where you are completely on your own in managing your asynchrony and concurrency via call-backs and other means.

Sorry, but I did not quite got what you were trying to achieve with your second example. Can you elaborate, please?

2 Likes