Trabalhando com filas de tarefas em Kotlin: para iniciantes
\nNo desenvolvimento moderno, surge frequentemente a necessidade de executar várias tarefas de forma assíncrona: processar downloads, enviar requisições para APIs, trabalhar com bancos de dados. Sem uma organização adequada do código, isso pode levar ao caos — as tarefas serão executadas em uma ordem imprevisível e os recursos do sistema ficarão sobrecarregados. As filas de tarefas (task queues) resolvem esse problema. Neste artigo, vamos explorar como criar e usar filas de tarefas em Kotlin com coroutines e canais (Channel). O material é voltado para desenvolvedores iniciantes familiarizados com os fundamentos do Kotlin.
\n\nO que é uma fila de tarefas e para que serve?
\nUma fila de tarefas é uma estrutura de dados que opera pelo princípio FIFO (First In, First Out — "o primeiro a entrar é o primeiro a sair"). Você adiciona uma tarefa ao final da fila, e um processador especial (worker) a retira do início e a executa. Isso permite:
\n- \n
- Organizar a execução. As tarefas não entram em conflito umas com as outras. \n
- Controlar a carga. Você pode limitar o número de tarefas executadas simultaneamente. \n
- Aumentar a responsividade da aplicação. Operações pesadas não bloqueiam a thread principal. \n
Em Kotlin, a principal ferramenta para trabalhar com filas assíncronas são as coroutines e seu componente Channel. São threads leves que não consomem muitos recursos.
Configuração do projeto para trabalhar com coroutines
\nAntes de escrever código, certifique-se de que as dependências necessárias estão incluídas no seu projeto. Se você usa Gradle, adicione ao arquivo build.gradle.kts (ou build.gradle):
dependencies {\n implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")\n implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") // para Android\n}\nAgora você está pronto para criar sua primeira fila.
\n\nCriando uma fila simples com Channel
\nChannel é essencialmente uma fila pronta que pode transmitir dados entre coroutines. Vamos ver um exemplo básico: temos um produtor (producer) que gera tarefas e um consumidor (consumer) que as executa.
import kotlinx.coroutines.*\nimport kotlinx.coroutines.channels.Channel\n\nfun main() = runBlocking {\n val channel = Channel<String>() // Criando um canal para strings\n\n // Produtor: envia tarefas\n launch {\n val tasks = listOf("Download de arquivo", "Processamento de imagem", "Envio de relatório")\n for (task in tasks) {\n println("Tarefa enviada: $task")\n channel.send(task) // Adiciona à fila\n delay(500) // Simula intervalo entre tarefas\n }\n channel.close() // Fecha o canal após enviar todas as tarefas\n }\n\n // Consumidor: executa tarefas\n launch {\n for (task in channel) { // Lê tarefas da fila\n println("Executando: $task")\n delay(1000) // Simula tempo de execução\n println("Concluída: $task")\n }\n }\n\n println("Thread principal continua trabalhando...")\n delay(5000) // Aguarda a conclusão\n}\nO que está acontecendo aqui?
\n- \n
Channel<String>()cria uma fila não bloqueante. \n send()adiciona um elemento,for (task in channel)— lê-os em ordem. \n - O canal bloqueia automaticamente a coroutine consumidora se a fila estiver vazia, e o produtor se a fila estiver cheia (por padrão, a capacidade é 0, ou seja, rendezvous). \n
A saída do programa será sequencial: as tarefas são executadas estritamente uma após a outra na ordem de adição.
\n\nGerenciando vários workers e bufferização
\nAplicações reais frequentemente exigem processamento paralelo. Suponha que temos 10 tarefas e queremos processá-las com