article banner

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.

suspend fun readDataFromSource(): Data { // ... } fun readDataFromSourceBlocking(): Data = runBlocking { readDataFromSource() }

We might also define a separate class that provides blocking API.

class AnkiConnectorBlocking { private val connector = AnkiConnector(/*...*/) fun checkConnection(): Boolean = runBlocking { connector.checkConnection() } fun getDeckNames(): List<String> = runBlocking { connector.getDeckNames() } fun pushDeck( deckName: String, markdown: String ): AnkiConnectorResult = runBlocking { connector.pushDeck(deckName, markdown) } fun pullDeck( deckName: String, currentMarkdown: String ): AnkiConnectorResult = runBlocking { connector.pullDeck(deckName, currentMarkdown) } }

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.

fun trainModelBlocking(model: Model): Model = runBlocking(Dispatchers.Default) { trainModel(model) }

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.

class AnkiConnectorFuture( private val connector: AnkiConnector, private val scope: CoroutineScope, ) { fun checkConnection(): CompletableFuture<Boolean> = scope.future { connector.checkConnection() } fun getDeckNames(): CompletableFuture<List<String>> = scope.future { connector.getDeckNames() } fun pushDeck( deckName: String, markdown: String ): CompletableFuture<AnkiConnectorResult> = scope.future { connector.pushDeck(deckName, markdown) } fun pullDeck( deckName: String, currentMarkdown: String ): CompletableFuture<AnkiConnectorResult> = scope.future { connector.pullDeck(deckName, currentMarkdown) } }

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.