Wondering if there’s something I’m missing here (or if this is the wrong approach):
- I have a core class whose methods can be called from many threads. Currently, I mark the methods as synchronized to make sure things are thread-safe.
- This core class manages some things beneath it. Mostly they are short, synchronous code paths but there is some logic which schedules some recurring tasks (some blocking, some non-blocking).
- I wanted to try out using coroutines here to:
- Have the core class execute all its logic within a single coroutine context (served by a single thread) to take care of the thread safety and avoid spinning up extra threads for the non-blocking, recurring tasks
- Schedule the non-blocking, recurring tasks in that same coroutine context
- Schedule the blocking, recurring tasks in a separate coroutine context, served by multiple threads
I have a short experiment with an aspect of this (everything except #3 above) pasted below:
val mainContext = newSingleThreadContext("main")
fun log(msg: String) {
println("[${Thread.currentThread().name}] $msg")
}
suspend fun doNonblockingSomething(): Boolean {
// Launch a recurring, non-blocking task that will be executed
// in the same context
launch (coroutineContext) {
repeat(5) {
log("loop #$it")
delay(1000)
}
}
log("end of doNonblockingSomething")
return true
}
class CoreClass {
// Transition from whatever the calling context is to run
// all code from here onward in 'mainContext'
suspend fun doThing(): Boolean = withContext(mainContext) {
doNonblockingSomething()
}
}
fun main(args: Array<String>) {
val core = CoreClass()
log("launching task")
// Bridge blocking code to coroutine code
val result = runBlocking {
val x = core.doThing()
log("got result $x")
x
}
log("main got result $result")
}
It outputs:
[main] launching task
[main @coroutine#1] end of doNonblockingSomething
[main @coroutine#2] loop #0
[main @coroutine#1] got result true
[main @coroutine#2] loop #1
[main @coroutine#2] loop #2
[main @coroutine#2] loop #3
[main @coroutine#2] loop #4
[main] main got result true
What surprised me was that the main runBlocking
block doesn’t finish until the coroutine launched inside doNonblockingSomething
finishes as well, even though doNonblockingSomething
returns. I suppose this is because blocks like runBlocking
wait until all coroutines (including any children) are finished. So what I’m wondering is: how can I spin off a child coroutine, executed in the current context, without blocking the parent one from the perspective of things like runBlocking
?