Threads vs Coroutines em Kotlin
1. Introdução
Neste tutorial rápido, vamos criar e executar threads em Kotlin.
Mais tarde, discutiremos como evitá-lo completamente, em favor deKotlin Coroutines.
2. Criando Threads
Criar um thread em Kotlin é semelhante a fazer isso em Java.
Poderíamos estender a classeThread (embora não seja recomendado porque Kotlin não oferece suporte a herança múltipla):
class SimpleThread: Thread() {
public override fun run() {
println("${Thread.currentThread()} has run.")
}
}
Ou podemos implementar a interfaceRunnable:
class SimpleRunnable: Runnable {
public override fun run() {
println("${Thread.currentThread()} has run.")
}
}
E da mesma forma que fazemos em Java, podemos executá-lo chamando o métodostart():
val thread = SimpleThread()
thread.start()
val threadWithRunnable = Thread(SimpleRunnable())
threadWithRunnable.start()
Como alternativa, como o Java 8, o Kotlin oferece suporte aSAM Conversions, portanto, podemos aproveitá-lo e passar um lambda:
val thread = Thread {
println("${Thread.currentThread()} has run.")
}
thread.start()
2.2. Função de Kotlinthread()
Outra maneira é considerar a funçãothread() que o Kotlin fornece:
fun thread(
start: Boolean = true,
isDaemon: Boolean = false,
contextClassLoader: ClassLoader? = null,
name: String? = null,
priority: Int = -1,
block: () -> Unit
): Thread
Com esta função, um thread pode ser instanciado e executado simplesmente por:
thread(start = true) {
println("${Thread.currentThread()} has run.")
}
A função aceita cinco parâmetros:
-
start – Para executar imediatamente o tópico
-
isDaemon - Para criar o thread como um thread daemon
-
contextClassLoader - Um carregador de classes para usar para carregar classes e recursos
-
name – Para definir o nome do tópico
-
priority - Para definir a prioridade da discussão
3. Corotinas Kotlin
É tentador pensar que gerar mais threads pode nos ajudar a executar mais tarefas simultaneamente. Infelizmente, isso nem sempre é verdade.
Criar muitos encadeamentos pode, na verdade, tornar um aplicativo com baixo desempenho em algumas situações; threads são objetos que impõem sobrecarga durante a alocação de objetos e a coleta de lixo.
Para superar esses problemas,Kotlin introduced a new way of writing asynchronous, non-blocking code; the Coroutine.
Semelhante aos encadeamentos, as corotinas podem executar simultaneamente, aguardar e se comunicar com a diferença de que criá-las é muito mais barata que os encadeamentos.
3.1. Contexto de co-rotina
Antes de apresentar os construtores de corotina que o Kotlin fornece imediatamente, precisamos discutir o contexto da corotina.
As corotinas sempre são executadas em algum contexto que é um conjunto de vários elementos.
Os principais elementos são:
-
Job - modela um fluxo de trabalho cancelável com vários estados e um ciclo de vida que culmina em sua conclusão
-
Dispatcher - determina qual thread ou threads a co-rotina correspondente usa para sua execução. Com o despachante,we can a confine coroutine execution to a specific thread, dispatch it to a thread pool, or let it run unconfined
Veremos como especificar o contexto enquanto descrevemos as corrotinas nas próximas etapas.
3.2. launch
Olaunch function is a coroutine builder which starts a new coroutine without blocking the current threade retorna uma referência à co-rotina como um objetoJob:
runBlocking {
val job = launch(Dispatchers.Default) {
println("${Thread.currentThread()} has run.")
}
}
Possui dois parâmetros opcionais:
-
context - O contexto em que a co-rotina é executada, se não for definido, herda o contexto doCoroutineScope de onde está sendo iniciada
-
start - As opções de início para a co-rotina. Por padrão, a rotina é agendada imediatamente para execução
Observe que o código acima é executado em um pool de threads de fundo compartilhado porque usamosDispatchers.Default which o inicia noGlobalScope.
Como alternativa, podemos usarGlobalScope.launch which usa o mesmo despachante:
val job = GlobalScope.launch {
println("${Thread.currentThread()} has run.")
}
Quando usamosDispatchers.Default ouGlobalScope.launch, criamos uma co-rotina de nível superior. Mesmo que seja leve, ele ainda consome alguns recursos de memória enquanto é executado.
Em vez de lançar co-rotinas emGlobalScope, como costumamos fazer com threads (threads são sempre globais), podemos lançar corrotinas no escopo específico da operação que estamos executando:
runBlocking {
val job = launch {
println("${Thread.currentThread()} has run.")
}
}
Neste caso, começamos uma nova co-rotina dentro do construtor de scoroutinerunBlocking (que descreveremos mais tarde) sem especificar o contexto. Assim, a co-rotina herdará o contexto derunBlocking.
3.3. async
Outra função que o Kotlin fornece para criar uma co-rotina éasync.
A funçãoasync cria uma nova co-rotina e retorna um resultado futuro como uma instância deDeferred<T>:
val deferred = async {
[email protected] "${Thread.currentThread()} has run."
}
deferred é um futuro cancelável sem bloqueio que descreve um objeto que atua como um proxy para um resultado que é inicialmente desconhecido.
Comolaunch,, podemos especificar um contexto no qual executar a co-rotina, bem como uma opção de início:
val deferred = async(Dispatchers.Unconfined, CoroutineStart.LAZY) {
println("${Thread.currentThread()} has run.")
}
Neste caso, lançamos a co-rotina usandoDispatchers.Unconfined que inicia as co-rotinas no thread do chamador, mas apenas até o primeiro ponto de suspensão.
Observe queDispatchers.Unconfined é uma boa opção quando uma co-rotina não consome tempo de CPU nem atualiza nenhum dado compartilhado.
In addition, Kotlin provides Dispatchers.IO that uses a shared pool of on-demand created threads:
val deferred = async(Dispatchers.IO) {
println("${Thread.currentThread()} has run.")
}
Dispatchers.IO é recomendado quando precisamos fazer operações intensivas de E / S.
3.4. runBlocking
Vimos anteriormenterunBlocking, mas agora vamos falar sobre isso com mais detalhes.
runBlocking é uma função que executa uma nova co-rotina e bloqueia a thread atual até sua conclusão.
A título de exemplo, no snippet anterior, lançamos a corotina, mas nunca esperamos o resultado.
Para aguardar o resultado, temos que chamar o método de suspensãoawait():
// async code goes here
runBlocking {
val result = deferred.await()
println(result)
}
await() é o que é chamado de função de suspensão. Suspend functions are only allowed to be called from a coroutine or another suspend function. Por esta razão, nós o incluímos em uma invocaçãorunBlocking.
UsamosrunBlocking em funçõesmain e em testes para que possamos vincular o código de bloqueio a outro escrito em estilo suspenso.
De maneira semelhante à de outros construtores de corotina, podemos definir o contexto de execução:
runBlocking(newSingleThreadContext("dedicatedThread")) {
val result = deferred.await()
println(result)
}
Observe que podemos criar um novo encadeamento no qual poderíamos executar a corotina. No entanto, um encadeamento dedicado é um recurso caro. E, quando não for mais necessário, devemos liberá-lo ou, melhor ainda, reutilizá-lo em todo o aplicativo.
4. Conclusão
Neste tutorial, aprendemos como executar código assíncrono e sem bloqueio, criando um encadeamento.
Como alternativa ao tópico, também vimos como a abordagem de Kotlin para usar corrotinas é simples e elegante.
Como de costume, todos os exemplos de código mostrados neste tutorial estão disponíveisover on Github.