How do I exit a runBlocking coroutine from another class (in Kotlin)?

Viewed 995

I'm trying to make my code exit the program when something happens, such as a user exit command. This is how I'm running my program:

fun main() = runBlocking {
    Bot().start()
}

Unfortunately, running exitProcess(0) or Thread.currentThread().interrupt() doesn't seem to do anything when called outside of start(), which is really inconvient in my use case.

Is there any way around this?

2 Answers
runBlocking {
    val job = launch { start() }
    // Process input via other APIs here. When done:
    job.cancel()
}

(You may want to look into channels to deliver Cmd objects into your command loop, and just close the channel when input is done.)

I ran into a same issue with runBlocking.

A little background: First of all, We had to use runBlocking in order to bridge the gap between an interface implementation of a library method and our app's component that can only be accessed on the MAIN/UI thread. Inside the implementation of this method we had to synchronously return a value, we had to switch our context from Background thread to UI thread to explicitly fetch this information on the UI thread. It was never our choice to use runBlocking but we had to as there was no other way.

The Problem: This runBlocking was blocking the other background thread (in a certain situation). It was never ending and it blocked the background thread which was running in a separate child process of our application. So I looked up for a solution and found below reported issue on Github.

According to an answer in a reported issue here

When runBlocking machinery detects that it is cancelled it should not terminate its event loop until the coroutine inside it is still active.

Then he further explains that

This way, the termination of the coroutine is processed in the running event loop and the event loop terminates after that.

The Solution: So I came up with this solution that worked for me mentioned below, you can use this higher-order function. This is a 100% working solution, tested & verified in production. NOTE: Please don't use withContext() instead of async() in the code snippet below as it will never work.

fun <T> runBlockingWithTimeout(
    lifecycleScope: LifecycleCoroutineScope?,
    timeoutMillis: Long = 500L,
    block: () -> T
): T? {
    return lifecycleScope?.let {
        runBlocking {
            withTimeoutOrNull(timeMillis = timeoutMillis) {
                async(lifecycleScope.coroutineContext) {
                    block.invoke()
                }.await()
            }
        }
    }
}
Related