Home > Mobile >  Kotlin coroutines within a Spring REST endpoint
Kotlin coroutines within a Spring REST endpoint

Time:02-15

Given a REST endpoint and two asynchronous coroutines each returning an integer, I want this endpoint to return their sum. The two functions (funA and funB) should run in parallel, in such a way that the whole computation should take ~3secs. I'm using SpringBoot 2.6.3 and Kotlin Coroutines 1.6.0. Here is my attempt:

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class Controllers {

    @GetMapping(
        value = ["/summing"]
    )
    fun summing(): String {

        val finalResult = runBlocking {
            val result = async { sum() }
            println("Your result: ${result.await()}")
            return@runBlocking result
        }
        println("Final Result: $finalResult")
        return "$finalResult"
    }

    suspend fun funA(): Int {
        delay(3000)
        return 10
    }

    suspend fun funB(): Int {
        delay(2000)
        return 90
    }

    fun sum() = runBlocking {
        val resultSum = async { funA().await()   funB().await() }
        return@runBlocking resultSum
    }
}

The problem is that this code does not compile as await() is not recognised as a valid method. If I remove await() the two functions are executed in series (total time ~5 secs) and instead of the expected result (100), I get:

Your result: DeferredCoroutine{Completed}@1c1fe804
Final Result: DeferredCoroutine{Completed}@48a622de

and so the endpoint returns "DeferredCoroutine{Completed}@48a622de".

I want the endpoint to return "100" instead and within ~3secs. How can I achieve this?

CodePudding user response:

You really messed this up ;-) There are several problems with your code:

  • Use runBlocking() only to use another runBlocking() inside it.
  • Use async() and immediately call await() on it (in summing()) - it does nothing.
  • funA().await() and funB().await() don't make any sense really. These functions return integers, you can't await() on already acquired integers.
  • Generally, using much more code than needed.

The solution is pretty simple: use runBlocking() once to jump into coroutine world and then use async() to start both functions concurrently to each other:

runBlocking {
    val a = async { funA() }
    val b = async { funB() }
    a.await()   b.await()
}

Or alternatively:

runBlocking {
    listOf(
        async { funA() },
        async { funB() },
    ).awaitAll().sum()
}

Or (a little shorter, but I consider this less readable):

runBlocking { 
    val a = async { funA() }
    funB()   a.await()
}

Also,runBlocking() is not ideal. I believe Spring has support for coroutines, so it would be better to make summing() function suspend and use coroutineScope() instead of runBlocking() - this way the code won't block any threads.

  • Related