1
\$\begingroup\$

I'm playing with ktor right now. If I understand this correctly, code in ktor's handlers need to be as asynchronous as possible. So I was trying to figure out how do you write synchronous (potentially expensive) code in a Kotlin coroutine. Here's what I've written:

    install(DefaultHeaders)
    install(CallLogging) {
        level = Level.INFO
    }
    install(Routing) {
       // More routes here

        get("/hello") {
            val key = async {
                // Emulating an expensive synchronous call by doing lots of calculations
                log.info("Calculating key")
                var sb = StringBuilder(32)
                for (i in 0..15) {
                    var sym: Byte = (Math.random() * 255).toByte()
                    for(j in 1..10000000) {
                        sym = (sym + Math.random() * 40).toByte()
                    }
                    sb.append(String.format("%02X", sym))
                }

                log.info("Ended key calc")

                sb.toString()
            }

            call.respondText("Hello again! Your key is ${key.await()}", ContentType.Text.Plain)
        }
    }
}

I'm wrapping my potentially expensive synchronous code in async to get a Deferred and then await it.

I think it works because I'm able to do simultaneous request to /hello and other routes.

Is this the correct way to do it? Do I even need to bother, or can I write synchronous code in ktor handlers? Thank you.

\$\endgroup\$
1
\$\begingroup\$

To start, I don't know Ktor, so what I'm going to tell is when Ktor doesn't do additional stuff, which is highly probably not the case.

Coroutines are managers for threads: they tell threads what to do and when to do it.
When you write a coroutine, everytime you come across a suspend-keyword, the thread that executes the task asks the coroutine what to do next. The coroutine can tell to continue with the task he was working on, but it can also tell the thread to do something else.

This is great if you have to work with triggers:

  • A database-call
  • A with triggers the threads
  • Another thread that returns something.

Instead of waiting, the coroutine can tell the thread to do something else and once it comes across the suspend-keyword to continue with the task if the trigger is fulfilled.

In your code, you introduce a side-task by adding the async-keyword. The next thing you do is telling to wait on this side-task. This means that apart from adding a suspend-keyword, it does nothing.

So, a coroutine is not for computation, but for waiting. However, coroutines can manage more than one thread. Giving the side-task to a side-thread and waiting for that side-thread to finish is something that coroutines are great for.

Therefor, your code could be better written as:

/**
 * A dedicated context for sample "compute-intensive" tasks.
 */
val compute = newFixedThreadPoolContext(4, "compute")

get("/"){
    val key = withContext(compute){
       //heavy computation
    }
    call.respond(key.toString())
}

The expanded example can be found here: https://ktor.io/samples/feature/async.html

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.