8

Suppose I have a blocking function because of some third party library. Something along these lines:


fun useTheLibrary(arg: String): String {
   val result = BlockingLibrary.doSomething(arg)
   return result
}

Invocations to BlockingLibrary.doSomething should run on a separate ThreadPoolExecutor.

What's the proper way (assuming there is a way) of achieving this with kotlin?

Note: I've read this thread but seems pretty outdated

Pablo Fernandez
  • 103,170
  • 56
  • 192
  • 232

1 Answers1

7

If the blocking code is blocking because of CPU use, you should use Dispatchers.Default. If it is network- or disk-bound, use Dispatchers.IO. You can make this into a suspending function and wrap the blocking call in withContext to allow this function to properly suspend when called from a coroutine:

suspend fun useTheLibrary(arg: String): String = withContext(Dispatchers.Default) {
   BlockingLibrary.doSomething(arg)
}

If you need to use a specific ThreadPoolExecutor because of API requirements, you can use asCoroutineDispatcher().

val myDispatcher = myExecutor.asCoroutineDispatcher()

//...

suspend fun useTheLibrary(arg: String): String = withContext(myDispatcher) {
   BlockingLibrary.doSomething(arg)
}

If your library contains a callback-based way to run the blocking code, you can convert it into a suspend function using suspendCoroutine() or suspendCancellableCoroutine(). In this case, you don't need to worry about executors or dispatchers, because it's handled by the library's own thread pool. Here's an example in the Retrofit library, where they convert their own callback-based API into a suspend function.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • thanks! is there an authoritative source for this? or some link where I can read more about it? – Pablo Fernandez Feb 28 '21 at 19:46
  • 1
    It's just what I've picked up from using and reading about coroutines for the past year or so. I recommend reading all of Roman Elizarov's (design lead of Kotlin coroutines up until recently becoming the lead for all of Kotlin) Medium posts on coroutines. But this one looks especially relevant: https://elizarov.medium.com/blocking-threads-suspending-coroutines-d33e11bf4761 It's slightly old, but doesn't use any obsolete concepts. (The `run` function mentioned in the message board thread you linked is obsolete.) – Tenfour04 Feb 28 '21 at 22:35
  • And one more question, how would you 'unwrap' the coroutine later? meaning, how you call a suspend function from normal non-coroutine code? – Pablo Fernandez Mar 01 '21 at 04:08
  • You can only call suspend functions from coroutines, so you’d have to launch a coroutine. – Tenfour04 Mar 01 '21 at 04:09
  • It's kind of a chicken egg problem now, I need to call it from a suspend function but I cannot make `main` a suspend function, so? – Pablo Fernandez Mar 01 '21 at 04:24
  • As long as you aren’t working with UI methods, that’s fine. If you’re working from a UI part of your app, you will most likely create a CoroutineScope that defaults to the Main Dispatcher. If you’re on Android, the framework already provides CoroutineScopes for Activities, Fragments, and ViewModels that are scoped to their lifecycles and use Dispatchers.Main by default. The reason for this convention I think is that UI elements can only be modified from the main thread, but you don’t want to block the main thread so suspend functions take care of that. Path of least friction (code noise). – Tenfour04 Mar 01 '21 at 04:25
  • Usually, the “egg” is a CoroutineScope, but you actually can mark `main()` as `suspend`. – Tenfour04 Mar 01 '21 at 04:39
  • Oh, sorry I should have clarified. I'm not on Android. I'm using kotlin on a backend service – Pablo Fernandez Mar 01 '21 at 13:33
  • 1
    I was responding to your deleted comment and just using Android as an example. In a UI, you would usually be working with CoroutineScopes that are encapsulated with the UI screens of an app. I haven’t done any backends, but maybe there’s an analogous type of encapsulated module for which you’d attach a CoroutineScope. – Tenfour04 Mar 01 '21 at 13:48
  • Does this look reasonable? https://gist.github.com/fernandezpablo85/678d660aef99d99937b133b1d2f6303c – Pablo Fernandez Mar 01 '21 at 14:34
  • 1
    I think so, although I think you have to be careful with a single-thread dispatcher, because it could maybe deadlock if a coroutine running on it calls a suspend function that starts an async task on the same dispatcher. Not sure. I think `Dispatchers.Default` has a minimum of two threads even on a single-core machine for this reason. – Tenfour04 Mar 01 '21 at 14:46