Ошибка Inappropriate blocking method call Kotlin

Привет. Сегодня мы разберем ошибку, которая встречается у тех, кто начинает осваивать корутины. Inappropriate blocking method call ошибка как правило возникает при вызове блокирующего метода из неблокирующего (асинхронного). Давайте рассмотрим на конкретном примере

Суть проблемы

Допустим, вы юзаете HttpUrlConnection для получения данных (код ниже я нашел в Сети на stackoverflow). Написали функцию, которая принимает ссылку, и уже «прикрутили» корутины, но вот незадача, openConnetion линтер IDEA выделяет.

suspend fun getData(link: String): String {     
    var result = ""
    withContext(Dispatchers.IO) {         
        with(URL(link).openConnection() as HttpURLConnection) {             
            try {                 
                result = inputStream.bufferedReader().readText()             
            } catch (e:Exception) {                 
                e.printStackTrace()             
            }         
        }     
     }     
     return result 
}

IntelliJ IDEA совершенно правильно выделяет эти строчки, показывая ошибку Inappropriate blocking method call… Здесь мы пытаемся вызвать блокирующий метод из suspend функции. В итоге ее выполнение блокируется из-за синхронного запроса в Сеть. Весь смысл использования корутин теряется.

Inappropriate blocking method - IntelliJ IDEA

Решение #1

Первое что приходит на ум, использовать асинхронный http клиент для получения данных, например ktor или HttpClient из Java 11. Выберем второй вариант, и набросаем функцию для получения данных.

fun getHttpAsync(link: String): String? {
    val httpclient = HttpClient.newBuilder().build()
    val httprequest = HttpRequest.newBuilder()
        .uri(URI.create(link))
        .build()

    return httpclient.sendAsync(httprequest, BodyHandlers.ofString())
        .join()
        .body()
}

В качестве входного параметра в функции мы передаем ссылку. Затем добавляем клиента (client) и запрос (request). HttpClient, пришедший на замену HttpUrlConnection может отправлять как синхронные, так и асинхронные запросы в Сеть. Нам нужен второй вариант, а именно метод sendAsync, который получает данные и возвращает «джавовский» CompletableFuture. Вызвав сначала join(), a потом body() мы получаем тело ответа в виде строки.

Вуаля! Проблема решена. Но зачем тогда использовать корутины? А если корутины ну очень хочется использовать, а возможности заменить блокирующий код на аналогичный асинхронный нет?

Решение Inappropriate blocking с корутинами

Возвращаем все обратно, а HttpClinet отправляем туда, где ему и место… И пишем метод getDataAsync, который принимая на вход ссылку возвращает строку.

suspend fun getDataAsync(url: String): String {

    return suspendCoroutine { continuation ->
        Thread(kotlinx.coroutines.Runnable {
            with(URL(url).openConnection() as HttpURLConnection){
                try {
                    continuation.resume(inputStream.bufferedReader().readText())
                } catch (ex: Exception) {
                    continuation.resumeWithException(ex)
                } finally {
                    disconnect()
                }
            }
        }).start()
    }

}

Чтобы все работало как надо, мы создаем новый поток с помощью Thread(), помещаем блокирующий код внутрь, и оборачиваем все это в блок suspendCoroutine { continuation ->}. Пока процесс выполняется в отдельном потоке, наша корутина «спит». Как только код закончит работу, мы передаем результат в continuation.resume(). Тот в свою очередь возвращает данные в suspend функцию, а в случае ошибки вызываем continuation.resumeWithException() куда передаем наш Exceprion.

В итоге, все работает как надо. Можно еще лимитировать количество потоков при помощи ThreadPool, но это уже выходит за рамки объяснения ошибки.

Оставить ответ