Generally speaking, main threads should be available to allow user-facing parts of an application to remain responsive. Long-running blocking
operations can significantly reduce threads' availability and are best executed on a designated thread pool.
As a consequence, suspending functions should not block main threads and instead move any long-running blocking tasks off the main thread. This can
be done conveniently by using withContext
with an appropriate dispatcher. Alternatively, coroutine builders such as launch
and async
accept an optional CoroutineContext
. An appropriate dispatcher could be Dispatchers.IO
for
long-running blocking IO operations, which can create and shutdown threads on demand.
For some blocking tasks and APIs there may already be suspending alternatives available. When available, these alternatives should be used instead
of their blocking counterparts.
This rule raises an issue when the call of a long-running blocking function is detected within a suspending function without the use of an
appropriate dispatcher. If non-blocking alternatives to the called function are known, they may be suggested (e.g. use delay(…)
instead
of Thread.sleep(…)
).
Noncompliant code example
Executing long-running blocking IO operations on the main thread pool:
class workerClass {
suspend fun worker(): String {
val client = HttpClient.newHttpClient()
val request = HttpRequest.newBuilder(URI("https://example.com")).build()
return coroutineScope {
client.send(request, HttpResponse.BodyHandlers.ofString()).body() // Noncompliant
}
}
}
Using inappropriate blocking APIs:
suspend fun example() {
...
Thread.sleep(1000) // Noncompliant
...
}
Compliant solution
Executing long-running blocking IO operations in an appropriate thread pool using Dispatcher.IO
:
class workerClass(
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
suspend fun worker(): String {
val client = HttpClient.newHttpClient()
val request = HttpRequest.newBuilder(URI("https://example.com")).build()
return withContext(ioDispatcher) {
client.send(request, HttpResponse.BodyHandlers.ofString()).body() // Compliant
}
}
}
Using appropriate non-blocking APIs:
suspend fun example() {
...
delay(1000) // Compliant
...
}