Coroutines: Force execution on certain thread

So, I have a certain piece of code that must be executed on a certain Android HandlerThread and should return a result. In a nutshell, a HandlerThread is a thread that contains an endless loop and processes a queue of posted runnables.

I would like to achieve that I have a suspending function within a class that implements CoroutineScope like this:

suspend fun createFoo(): Foo {
    // create foo on certain HandlerThread and return suspending
}

So, my initial thought was that there may be a builder for a CoroutineDispatcher in the kotlinx-coroutines-android library that lets me select a HandlerThread like this:

suspend fun createFoo(): Foo {
    return withContext(HandlerContext(myHandler)) {
        doThingToCreateFoo()
    }
}

But there isn’t. (Not sure if withContext would be correct here anyway). So, I was thinking I use a Future that I launch in the mentioned thread and await the result, like this:

suspend fun createFoo(): Foo {
    val result = FutureTask<Foo>(Callable { doThingToCreateFoo() })
    myHandler.post { result.run() }
    val deferred = async { result.get() } // .get blocks the current thread until a result is available
    return deferred.await()
}

I am not sure if this behaves as I expect it to behave at all, and also from the IDE (IntelliJ) get two warnings I don’t understand:

  • For async: "Ambiguous coroutineContext due to CoroutineScope receiver of suspend function
  • For FutureTask.get: “Inappropriate blocking method call”

So, maybe someone can help me and explain what I got wrong.

The method you want is asCoroutineDispatcher.

withContext(myHandler.asCoroutineDispatcher()) {
   createFoo() 
} 
2 Likes

As for the errors:

Dispatchers are like handlers except they can use a thread pool. Blocking calls block threads in the pool from doing work. Block all of them and you can even deadlock. Suspending calls simply return early under the covers so the event loop can continue and then resume by posting a task back to the event loop. So avoid blocking calls in coroutines except for on the IO dispatcher which is designed for them (it always creates new threads when needed so it never is completely blocked).

I’d need more context to understand the other error completely. I’m guessing you just want to explicitly specify the this that you want to call async on.

Thank you for the info!

So generally, how would one go about transforming blocking stuff (waiting for HTTP answer, DB access etc) to a suspending function? For example, I guess

suspend fun sleep() {
    Thread.sleep(1000)
}

is not ok, but what would be? Wrap it in withContext(Dispatchers.IO) { ... } ?

Regarding the warning on FutureTask, I guess this was related to that since FutureTask.get() blocks, there should be some kind of handling for interrupt. The warning didn’t give much more context.

If you HAVE to call a blocking method, you should switch to the IO dispatcher with withContext as you proposed. However, if possible you should break up your coroutines by returning control to the thread either by using delay() to suspend your function for some time or by invoking yield() to return control to the thread without a delay. Note that both delay() and yield() will throw CancellationException when the coroutine is cancelled, so using these will allow you to support cancellation automatically.

For example, reading a stream could be done asynchronously AND support cancellation by doing the following:

suspend fun readStream(stream: FileInputStream) {
    var nextByte = stream.read()
    // -1 is returned when there's nothing left in the file
    while (nextByte != -1) {
        print(nextByte.toChar())
        yield() // Return control to the worker thread, IRL you may want to do this less frequently
        nextByte = stream.read()
    }
}

FileInputStream.read is a blocking call. Please do not call it in a coroutine with out using Dispatchers.IO. It is no different than any other IO.