How to correctly make blocking calls in Kotlin Cor...
# feed
h
How to correctly make blocking calls in Kotlin Coroutines? It might suprise you, but IO dispatcher is not the best answer! Learn about it in this new article by @marcinmoskala. https://kt.academy/article/interop-blocking-to-coroutines
a
In the blog it says that IO is the best answer
m
This post says IO is not the best answer
k
<http://Dispatchers.IO|Dispatchers.IO>.limitedParallelism(n)
is the best answer?
m
There is no one best answer, it is one section in the article. I felt I presented it clearly.
👍 1
a
What is the reason that using Loom dispatcher does not cause the issues you mention? In the end the amount of threads is limited right, whether or not it is abstracted away with coroutines or virtual threads?
m
It is because in look threads get blocked, so just like in Kotlin Coroutines you can have thousends of suspended coroutines in a single thread, in Loom you can have thousends of blocked threads using only one real thread.
a
But how is that better than coroutines?
m
Better when you need to use a blocking API
1
w
There's an important API that I think must be mentioned in this topic. It's
runInterruptible {}
, and I've been waiting for a long time for someone to post an article about whether
runInterruptible
is more efficient on virtual threads because I've been procrastinating investigating it myself :p Naively I'd expect
runInterruptible
to be orders of magnitude more efficient on virtual threads, and it should be a no-brainer to wrap all blocking IO calls in a virtual thread dispatcher +
runInterruptible
. But my rudimentary benchmark doesn't show as much of a performance improvement as I expected. (I've attached my benchmark result of cancelling a bunch of
runInterruptible
coroutines using
<http://Dispathers.IO|Dispathers.IO>
and a virtual thread dispatcher, but please take it with a bucket of salt! This is NOT a proper benchmark)
m
Why do you think it should be more efficient?
Regarding IO vs Loom, Loom will surely be more efficient for many coroutines with multiple blocking operations, no questions asked. Run this
Copy code
suspend fun main() = coroutineScope {
    repeat(1000) {
        launch(Dispatchers.Default) {
            Thread.sleep(1000)

            val threadName = Thread.currentThread().name
            println("Running on thread: $threadName")
        }
    }
}
vs
Copy code
suspend fun main() = coroutineScope {
    val dispatcher = Executors.newVirtualThreadPerTaskExecutor()
        .asCoroutineDispatcher()
    repeat(1000) {
        launch(dispatcher) {
            Thread.sleep(1000)
            println("Running")
        }
    }
}
The first one should take around 16 seconds, the second one 1-2 seconds.
runInterruptible
is AFAIK primarly a JVM-specific
withContext
with interruption mechanism
w
I'm sorry, but this is not a fair benchmark, you're not comparing virtual threads vs system threads here. 😅 The overhead of creating a virtual thread vs system thread is completely negligible compared to the sleep here. The only thing this benchmark shows is ratio between the limited parallelism of
Dispatchers.Default
vs that of the loom thread dispatcher. If I use
<http://Dispatchers.IO|Dispatchers.IO>.limitedParallelism(1000)
vs
Executors.newVirtualThreadPerTaskExecutor().asCoroutineDispatcher()
, it results in
1.074489041s
vs
1.053347041s
, which is not a result anyone should take any conclusions from imho. I'm not denying that virtual threads might be faster for IO, I was instead curious about cancellation performance.
runInterruptible
is an extremely useful utility that allows you to cancel IO calls on the JVM. Yet barely anyone uses it, it has literally 77 times less usages than
<http://Dispatcher.IO|Dispatcher.IO>
(21k vs 271 public usages). I assume that the reason is that people are worried about the cost of interrupting a thread, because they are expensive. But this could in theory be negated with virtual threads from what I naively expect. But noone talks about it for some reason...
m
Ok, I think I see your point. You want to use
runInterruptible
to have faster cancellation together with Loom dispatcher to avoid costs of recreating threads. It might make sense in some kinds of system, like those with CPU-intensive non-suspending functions, however in most cases cancellation mechanism should be quite efficient, and I wouldn't expect any significant improvement from replacing it with thread interruption.
w
Interestingly, that's not correct :). Cancellation is not efficient at all by default with IO tasks. IO tasks will not be canceled. So you're waiting for the IO operation to finish before you can start canceling the coroutine tree. Especially on non-jvm platforms you need to hope that the IO APIs you're calling allow you to cancel. Luckily, on JVM you have
runInterruptible
!
m
Oh, I forgot you started from blocking tasks use case, in such a case it might be an improvement similar to the amount of extra time this blocking operation requires