«Inappropriate blocking method call»: quando o Kotlin reclama e você não entende porquê
\n\nVocê programa em Kotlin, usa corrotinas, tudo parece ideal. E então — bum! — o compilador ou analisador estático (por exemplo, IntelliJ IDEA) emite um aviso: «Inappropriate blocking method call». Familiar? Se você já viu essa mensagem pelo menos uma vez, sabe: ela aparece no momento mais inoportuno. E se não viu, considere-se com sorte, mas é melhor se preparar com antecedência.
\n\nEste erro não é apenas um capricho da IDE. É um sinal: você está fazendo algo que pode "matar" o desempenho da sua aplicação. Neste artigo, vamos analisar o que significa este aviso, por que ele ocorre e, principalmente, como tornar o "inappropriate blocking method call" apropriado (ou seja, adequado). Vamos comparar três abordagens, escrever código em Kotlin e descobrir quando cada método é melhor. Vamos nessa!
\n\nO que é «inappropriate blocking method call»?
\n\nEm poucas palavras: Kotlin (e seu ecossistema) é construído sobre a ideia de execução não bloqueante. Quando você usa corrotinas, presume-se que você não vai "dormir" a thread com chamadas como Thread.sleep() ou InputStream.read(). Isso bloqueia a thread, enquanto as corrotinas, ao contrário, devem liberá-la.
Um exemplo típico é a chamada Thread.sleep(1000) dentro de uma corrotina. O analisador vê isso e grita: "Ei! Você está bloqueando a thread! Use delay()!". Mas o problema não é apenas o sleep. Qualquer chamada que possa bloquear a thread (leitura de arquivo, trabalho com BD, requisição HTTP síncrona) pode causar este aviso.
Por que isso é ruim? Porque as corrotinas trabalham em um pool de threads. Se uma thread é bloqueada, ela não pode executar outras corrotinas. No pior caso, você corre o risco de ter "fome" de threads e queda de desempenho.
\n\nComparação de abordagens: como corrigir a chamada bloqueante
\n\nVamos considerar três cenários onde o erro ocorre e mostrar como contorná-lo. Para cada abordagem, haverá um exemplo de código em Kotlin e análise de prós e contras. No final, uma tabela resumo.
\n\nAbordagem 1: Usando Dispatchers.IO
\n\nA maneira mais simples e frequentemente recomendada é envolver o código bloqueante em withContext(Dispatchers.IO). Este dispatcher é especificamente projetado para operações de entrada e saída bloqueantes.
import kotlinx.coroutines.*\n\nsuspend fun readFile(path: String): String = withContext(Dispatchers.IO) {\n // Chamada bloqueante (por exemplo, leitura de arquivo)\n java.io.File(path).readText()\n}\n\nfun main() = runBlocking {\n val content = readFile("example.txt")\n println(content)\n}\n\nPrós:
\n- \n
- Mínimas alterações no código. \n
- Separação clara: o código bloqueante é executado em um pool de threads separado. \n
- Não requer bibliotecas de terceiros. \n
Contras:
\n- \n
- Ainda bloqueia uma thread (embora não seja a principal). \n
- Com um grande número de chamadas simultâneas, pode esgotar o pool de threads (padrão de 64 threads). \n
Quando usar: Se você tem operações bloqueantes raras (por exemplo, carregar configuração na inicialização) ou não pode alterar a biblioteca.
\n\nAbordagem 2: Substituição por análogos não bloqueantes
\n\nA opção ideal, mas nem sempre possível, é substituir a chamada bloqueante por sua versão não bloqueante. Por exemplo, em vez de Thread.sleep(), usar delay(); em vez de um cliente HTTP síncrono, um assíncrono (Ktor, OkHttp com corrotinas).
import kotlinx.coroutines.*\n\nsuspend fun fetchData(): String {\n // Atraso não bloqueante\n delay(1000)\n // Requisição HTTP assíncrona (exemplo com Ktor)\n return httpClient.get("https://api.example.com/data").body()\n}\n\nfun main() = runBlocking {\n val data = fetchData()\n println(data)\n}\n\nPrós:
\