Search code examples
multithreadingkotlinasynchronousconcurrencykotlin-coroutines

Kotlin: How to bridge between blocking and asynchronous code without runBlocking?


I'm working on a project that involves computationally intensive tasks that I want to run in parallel. To do so, I'm using multiple async statements to run the tasks and awaitAll to wait until all threads completed computation.

suspend fun tasks() {
   coroutineScope {
      val result = List (10) {
         async { 
            // do stuff
         }
      }.awaitAll()
   }
}

My question is how to bridge between this code that is run in parallel and regular synchronous code.

I tried to use runBlocking but that seems to run all the async tasks after one another, therefore defeating the whole purpose of using coroutines. The only way I got it to work was to use suspend functions all the way up to the main function, however that is not suitable in my case, as I rely on third-party libraries to call my code from regular functions.

Is there a way to call suspend functions from regular functions while still maintaining their ability to run in parallel?


Solution

  • The reason why subtasks were running sequentially is that by default runBlocking() utilizes the thread that invoked it to create a single-thread dispatcher and runs all coroutines using it. If your coroutines never suspend, they will be invoked sequentially.

    In order to use another dispatcher, we simply need to pass it to runBlocking():

    runBlocking(Dispatchers.Default) { ... }
    

    However, if you don't use coroutines already and you don't plan to suspend, but only perform CPU-intensive tasks, then I'm not sure if you can at all benefit from coroutines. Coroutines are mostly for performing tasks that often have to wait for something, so we can utilize their suspend & resume feature. They are useful, if we need to often fork and join. But if we simply need to parallelize CPU computation, then the classic approach with executors will do.

    Also, when bridging using runBlocking(), be careful to not invoke it from the coroutine itself. We can't block the thread while running inside the coroutine context. It could cause serious performance problems or even a deadlock. If you invoke runBlocking(), and then inside it, somewhere deep into the call stack, you invoke one of your bridges again, you will run into problems.