2

This question arose after extensive discussion with a coworker,

The issue is the following:

what I was doing before calling a synchronous method inside a suspendCoroutine block with continuation:

private suspend fun <T : Any> request(queryRequest: GraphQLQueryRequest<T>): T? = suspendCoroutine { continuation ->
    val response = graphqlClient.executeQuery(queryRequest.query, queryRequest.variables, executor)
    val item = response.extractValueAsObject("data.${queryRequest.pathAccessor}", queryRequest.type.java)
    continuation.resume(item)
}

He claimed that my code was not preventing the thread from being blocked and the whole process would have to wait my coroutine resume to release the thread.

What he proposed:

private suspend fun <T : Any> request(queryRequest: GraphQLQueryRequest<T>): T? {
    val response = graphqlClient.reactiveExecuteQuery(queryRequest.query, queryRequest.variables, executor)
    return response.awaitSingle().extractValueAsObject("data.${queryRequest.pathAccessor}", queryRequest.type.java)
}

reactiveExecuteQuery() will return WebFlux Mono, then we call .awaitSingle() to convert Mono into a suspend function, he claims that this method unlike the first one will not block the thread and everything will work just fine.

according to documentation for suspendCoroutine

Obtains the current continuation instance inside suspend functions and suspends the currently running coroutine.

Does anyone with more insight into Kotlin coroutines know if those solution really provide different results? in terms of blocking of no blocking threads?

If that is not a use case for suspendCoroutine what would it be?

Thank you in advance for any replies :)

Alexey Soshin
  • 16,718
  • 2
  • 31
  • 40
Lucas
  • 746
  • 8
  • 23

1 Answers1

3

You coworker was generally right, as @Martin Tarjányi already mentioned.

awaitSingle is a suspending function, as you can see in the documentation. It means that invoking it will suspend the coroutine, but other coroutines can still run on the same Dispatcher.

Sometimes, there is no other solution, when a library provides only blocking API, but then you should be running your coroutines on a relatively large thread pool. With your code, you would run on the same dispatcher your request() is invoked from, usually Dispatchers.Default, which equals to the number of your cores (or 2 in case you run on a single core CPU).

Alexey Soshin
  • 16,718
  • 2
  • 31
  • 40
  • 1
    You can mitigate the problem when you have to call blocking code by wrapping it in `withContext(Dispatchers.IO)` since IO has a relatively large thread pool. – Tenfour04 Sep 24 '21 at 04:50