I have an app with 2 background tasks. The main app communicates with these via LinkedBlockingQueues. Tasks to be executed are put on the appropriate queue. The background tasks wait on these queues. When they receive a task they execute it and then go back to waiting.
Starting the background task:
if (schedulerJob == null){
debugInst.debug("PriceLoader", "initializeExtension", Debug.INFO, "starting Scheduler")
schedulerJob = coroutineScope.launch { Scheduler(coroutineScope.coroutineContext) }
}
I ran into a problem when looking at the Cancelling of the background tasks. According to the coroutine documentation the cancelling of tasks is a handshake where the calling task cancels the background task but the background task has to recognize it has been cancelled and terminate.
If I use the LinkedBlockingQueue.take action it will not exit if the background task is cancelled.
while (true) {
var commandLine = Main.schedulerQueue.take()
if (isActive) {
processCommand(commandLine)
} else {
debugInst.debug("Scheduler", "init", Debug.SUMMARY, "Scheduler canceled")
break
}
}
I believe this will hang indefinitely. So i changed it to using poll:
while (true) {
var commandLine = Main.schedulerQueue.poll(1L, TimeUnit.SECONDS)
if (isActive) {
processCommand(commandLine)
}
} else {
debugInst.debug("Scheduler", "init", Debug.SUMMARY, "Scheduler canceled")
break
}
}
The question is: Will cancelling a coroutines Job stop the thread or will it wait until the thread terminates? Has anyone implemented a similar process that is cleaner?
Cancelling a Coroutine Job doesn’t stop any thread; instead, it stops the coroutine execution. Coroutines run on a thread pool.
In the following, isActive should be false when you cancel the Job.
while (true) {
var commandLine = Main.schedulerQueue.poll(1L, TimeUnit.SECONDS)
if (isActive) {
processCommand(commandLine)
}
} else {
debugInst.debug("Scheduler", "init", Debug.SUMMARY, "Scheduler canceled")
break
}
}
I think you are doing wrong here
schedulerJob = coroutineScope.launch {
Scheduler(coroutineScope.coroutineContext)
}
update it to
schedulerJob = coroutineScope.launch {
Scheduler(this.coroutineContext) // 'this' is the CoroutineScope of this launch
}
Is there a reason why you’re mixing blocking APIs with coroutines? What about using Channel or SharedFlow instead of LinkedBlockingQueue?
1 Like
Thanks davidecannizzo, the simple answer is lack of knowledge. This is my first venture into Kotlin (from Java). I will investigate.
Neeraj_Sharma, thanks for the reply. The launching of the tasks works fine, it is the communication between the tasks I am having a problem with. What I didn’t show is that the Scheduler class extends CoroutineScope
class Scheduler(override val coroutineContext: CoroutineContext) : CoroutineScope {
so the my call is correct
Definitely look at channels.
Thanks everyone for your input. I have successfully moved to Channels
Perfect. I’ll a couple general suggestions for concurrency, that is:
-
Always check out if there’s a non-blocking API first; whether it’s a suspend fun (preferably) or an API with a callback (in which case you’d use suspendCancellableCoroutine to turn it into a cancellation-aware coroutine).
-
When there’s no alternative but to use a blocking API, you should be aware that you can only run one per thread, so you must manage threading carefully, and you can’t safely cancel those unless they support cooperative cancellation. If you’re on the JVM and dealing with IO-bound operations it makes sense to use green threads (Thread.ofVirtual().start()) for more lightweight scheduling and less memory footprint. Normal, i.e. preemptive scheduled, threads are necessary for CPU-bound operations.