In Kotlin, is it possible to substitute a suspend fun with a non-suspend version, without breaking the caller?

Viewed 1193

I'm learning concurrency in Kotlin, coming from C#/JavaScript background, and I can't help comparing some concepts.

In C# and JavaScript, technically we can rewrite an async function as a regular non-async version doing the same thing, using Task.ContinueWith or Promise.then etc.

The caller of the function wouldn't even notice the difference (I ranted about it in a blog post).

Is something like that possible for a suspend function in Kotlin (i.e., without changing the calling code)? I don't think it is, but I thought I'd still ask.

The closest thing I could come up with is below (Kotlin playground link), I still have to call .await():

import kotlinx.coroutines.*

suspend fun suspendableDelay(ms: Long): Long {
    delay(ms);
    return ms;
}

fun regularDelay(ms: Long): Deferred<Long> {
    val d = CompletableDeferred<Long>()
    GlobalScope.async { delay(ms); d.complete(ms) }
    return d;
}

suspend fun test(ms: Long): Long {
    delay(ms);
    return ms;
}

fun main() {
    val r1 = runBlocking { suspendableDelay(250) }
    println("suspendableDelay ended: $r1");
    val r2 = runBlocking { regularDelay(500).await() }
    println("regularDelay ended: $r2");
}

https://pl.kotl.in/_AmzanwcB

2 Answers

This can only work if you change your suspending function to a non-suspending blocking function, for example

private fun method(){
    GlobalScope.launch {
        val value = getInt()
    }
}

// Calling coroutine can be suspended and resumed when result is ready
private suspend fun getInt(): Int{
    delay(2000)  // or some suspending IO call
    return 5;
}

// Calling coroutine can't be suspended, it will have to wait (block)
private fun getInt(): Int{
    Thread.sleep(2000)   // some blocking IO 
    return 5;
}

Here you can simply use the non-suspending version, without any change on the caller.

But the issue here is that without suspend modifier the function becomes blocking and as such it can not cause the coroutine to suspend, basically throwing away the advantage of using coroutiens.

If you're on JVM 8 or higher, you can make a function that calls the suspend function in an async job and returns a CompletableFuture, which can be used to get your result with a callback (thenApplyAsync()) or synchronously (get()).

val scope = CoroutineScope(SupervisorJob())

suspend fun foo(): Int {
    delay(500)
    return Random.nextInt(10)
}

fun fooAsync(): CompletableFuture<Int> = scope.async { foo() }.asCompletableFuture()

fun main() {
    fooAsync()
        .thenApplyAsync { println(it) }
    Thread.sleep(1000)
}

The above requires the kotlinx-coroutines-jdk8 library.

I don't know of a solution that works across multiple platforms.

Related