3

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

noseratio
  • 59,932
  • 34
  • 208
  • 486

2 Answers2

2

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.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154
1

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.

mightyWOZ
  • 7,946
  • 3
  • 29
  • 46
  • Tackling it the other way around, maybe there is a way to attach a completion callback to `Deferred` without resorting to `async {}` scope (like I did in `regularDelay`)? I've noticed there's [`Deferred.onAwait`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/on-await.html), but I haven't figured out yet how it can be used. – noseratio Aug 13 '21 at 09:29
  • 1
    Ultimate issue is that you can't suspend, and since any async callback will not be invoked immediately, you will have to wait and block the calling scope in order to return the required result – mightyWOZ Aug 13 '21 at 10:00