69 lines
3.0 KiB
Plaintext
69 lines
3.0 KiB
Plaintext
== Why is this an issue?
|
|
|
|
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:
|
|
[source,kotlin]
|
|
----
|
|
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:
|
|
[source,kotlin]
|
|
----
|
|
suspend fun example() {
|
|
...
|
|
Thread.sleep(1000) // Noncompliant
|
|
...
|
|
}
|
|
----
|
|
|
|
=== Compliant solution
|
|
Executing long-running blocking IO operations in an appropriate thread pool using `Dispatcher.IO`:
|
|
[source,kotlin]
|
|
----
|
|
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:
|
|
[source,kotlin]
|
|
----
|
|
suspend fun example() {
|
|
...
|
|
delay(1000) // Compliant
|
|
...
|
|
}
|
|
----
|
|
|
|
== Resources
|
|
|
|
* https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html[Coroutine context and dispatchers]
|
|
* https://developer.android.com/kotlin/coroutines/coroutines-best-practices#main-safe[Suspend functions should be safe to call from the main thread] (Android coroutines best practices)
|
|
* https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-i-o.html[IO CoroutineDispatcher]
|
|
* https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html[Default CoroutineDispatcher]
|