Suspending functions into blocking functions or CompletableFuture
Before Kotlin Coroutines, many projects were based on threads that call blocking functions. This is still a common pattern in many libraries, and still many projects use it. That is why when we use Kotlin Coroutines, we often need to interoperate with such an approach. In a previous article I showed how to turn blocking functions into suspending functions, and in this article I will show how to turn suspending functions into blocking functions.
This article is a part of a series about interoperability between Kotlin Coroutines and other libraries. This series includes:
Turning suspending functions into blocking functions
Suspending functions cannot be called from regular functions. If we define a service that provides suspending functions for usage from coroutines, such functions cannot be called in blocking code. That might be a problem if our project has legacy blocking code, or if our library wants to provide alternative API for non-coroutine users. The simplest solution is to provide an alternative blocking API that wraps our suspending functions with runBlocking
. This will allow us to call suspending functions from blocking code, but it will block the thread until the suspending function finishes.
We might also define a separate class that provides blocking API.
runBlocking
is generally an idiomatic way to turn suspending functions into blocking functions. The way how it works is that it starts a new coroutine and uses the current thread to execute it, and its children. This thread is not released until all those coroutines finish. It also returns the result of its body.
The only potential problem with runBlocking
is that it uses the same thread not only for its body, but also for all its children. That means that if our function needs to start many coroutines, they will all run on the same thread. To change that, we can use a different dispatcher.
Turning suspending functions into CompletableFuture
In modern Java application, it is more and more common to use functions that result with CompletableFuture
. Such functions are very convenient, as they start an asynchronous process on a thread, and allow us to await its completion at any place in the code. That allows us to simply start multiple asynchronous operations together, and synchronize their results later wherever we need them.
To turn a suspending function into a CompletableFuture
, we use future
on a scope. That starts an asynchronous coroutine, that can be controlled by the CompletableFuture
.
If you design an API for Java or Kotlin non-coroutine users, prefer
CompletableFuture
over blocking functions. It is more idiomatic in modern Java applications, and it allows users to use the API in a more flexible way.
Conclusion
In this article, I showed how to turn suspending functions into blocking functions and CompletableFuture
. This allows us to interoperate with legacy blocking code and libraries that use CompletableFuture
. The idiomatic way to turn suspending functions into blocking functions is to use runBlocking
, while the idiomatic way to turn them into CompletableFuture
is to use future
on a scope.