I worked out how to keep application alive until a...
# coroutines
d
I worked out how to keep application alive until all tasks are completed. Is there a better way to do this with coroutines though?
Copy code
private val termination = CountDownLatch(1)
private val scope = dispatcher + Job().apply {
    thread {
        // use non-dameon thread to keep application alive until all tasks completed.
        termination.await()
        while (children.any { it.isActive }) Thread.sleep(50)
    }
}

fun shutdown() {
    termination.countDown()
}
🙀 1
s
Why not just?
Copy code
val scope : CoroutineScope = ...
fun main() = {
  val job = scope.launch { ... do all my tasks within this coroutine ... }
  runBlocking { job.join() }
}
d
This scope is used for event processing and it has no set moment when it stops
but that could work
But I can't call
join()
on a forever active parent Job no?
There might be something like joinChildren() actually
s
Why not do a
runBlocking { job.join() }
instead of
while (children....) { ... }
? (the
job
is the
Job()
of your scope)
d
But the
Job()
of my scope is never cancelled
s
RIght, I see… your scope is never cancelled, but the spawned child-coroutines (jobs) have ended….
d
Copy code
runBlocking {
 scope.coroutineContext[Job]!!.children.toList().joinAll()
}
💯 1
^^ that doesn't work
s
Hmmmm…
d
and calling
join()
on the parent job causes the thread to hang indefinitely
s
This code works for me:
Copy code
fun main() {
    val parentJob = Job()
    val scope = CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO> + parentJob)

    println("Start")

    val job = scope.launch {
        launch { delay(1000) }
        launch { delay(2000) }
        launch { delay(2000) }
        launch { delay(2000) }
        launch { delay(2000) }
        launch { delay(2000) }
        launch { delay(2000) }
        launch { delay(2000) }
        launch { delay(2000) }
    }
    runBlocking { parentJob.children.toList().joinAll() }

    println("Done")
}
(it also works when replacing
parentJob
with
job
inside the
runBlocking { ... }
)
(
parentJob
has only one child,
job
has 9 children)
d
I think there are some complications in my code that cause this to happen
testing something now
Yep ok, it's working
bit of a complex setup that I simplified too much I guess
Thanks for the help!
s
You’re welcome! 🙂 Great you got it working
g
Maybe I didn't get the use case, but why not just replace scope.launch with runBlocking
Getting children jobs looks like a hack for me
d
You didn't get the use case indeed
Perhaps it isn't the most clear but I used a solution with runBlocking, which works if called with a non daemon thread
u
How about making all scopes in your app children of a root scope and await this root scope in your thread?
d
I would need to cancel the root scope right?
u
Right
d
Not an option, I wouldn't be able to start cleanup jobs
u
Can't you start the cleanup job ina different context? It could be even non cancelable
d
What I would practically need is for a channel to be closed when the parent job completes or gets cancelled, with a non cancellable coroutine that runs and processes all remaining items from the channel before dieing. They would have to be processed on a different scope which would need to be a child of the original parent, because the application must not close before those tasks are completed. The child scope would need to prevent its cancellation before receiving the cleanup tasks and then cancel itself when the cleanup tasks have all completed, allowing the original parent to finally complete and for the application to shut down.
There may be no items left in the channel, so there may also be no cleanup tasks. I think the child scope could be a child of the coroutine that processes items from the channel, starting only non cancellable tasks. That is probably a solution.
g
channel to be closed when the parent job completes or gets cancelled
Sounds like you need Flow
d
I'm sending items for processing to the channel from other threads