Ilha KotlinTextos anteriores desta série: sobre AsyncTask , sobre Loaders , sobre Executors e EventBus , sobre RxJava .Então chegou esta hora. Este é o artigo para o qual toda a série foi escrita: uma explicação de como a nova abordagem funciona "sob o capô". Se você ainda não sabe como usá-lo, aqui estão alguns links úteis para você começar:
E, ao dominar as corotinas, você pode se perguntar o que permitiu à Kotlin oferecer essa oportunidade e como ela funciona. Observe que aqui vamos nos concentrar apenas no estágio de compilação: você pode escrever um artigo separado sobre execução.
A primeira coisa que precisamos entender é que, no corpus, na verdade não existem corotinas. O compilador transforma a função com o modificador de suspensão em uma função com o parâmetro
Continuation . Essa interface possui dois métodos:
abstract fun resume(value: T) abstract fun resumeWithException(exception: Throwable)
Tipo T é o tipo de retorno da sua função de suspensão original. E aqui está o que realmente acontece: essa função é executada em um determinado segmento (paciência, também chegamos a isso), e o resultado é passado para a função resume dessa continuação, no contexto em que a função de suspensão foi chamada. Se a função não receber o resultado e gerar uma exceção, o resumeWithException será lançado, gerando um erro no código de chamada.
Ok, mas de onde veio a continuação? Claro, do construtor Corutin! Vejamos o código que cria qualquer corotina, por exemplo, o lançamento:
public actual fun launch( context: CoroutineContext = DefaultDispatcher, start: CoroutineStart = CoroutineStart.DEFAULT, parent: Job? = null, block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context, parent) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine }
Aqui, o construtor cria uma corotina - uma instância da classe AbstractCoroutine, que, por sua vez, implementa a interface de Continuação. O método start pertence à interface Job. Mas encontrar a definição do método de início é muito difícil. Mas podemos vir aqui do outro lado. Um leitor atento já percebeu que o primeiro argumento para a função de inicialização é o CoroutineContext, e está definido como DefaultDispatcher por padrão. Despachantes são classes que controlam a execução de corotinas, portanto são definitivamente importantes para entender o que está acontecendo. Vejamos a declaração DefaultDispatcher:
public actual val DefaultDispatcher: CoroutineDispatcher = CommonPool
Então, na verdade, esse é o CommonPool, embora as docas java nos digam que isso pode mudar. O que é o CommonPool?
Este é um gerenciador de rotinas que usa o
ForkJoinPool como uma implementação do ExecutorService. Sim, é: no final, todas as suas rotinas lambda são apenas Runnable, que entraram no Executor com um conjunto de transformações complicadas. Mas o diabo, como sempre, está nos detalhes.
Garfo? Ou participar?A julgar pelos resultados da pesquisa no meu twitter, aqui preciso explicar brevemente o que é FJP :)
Primeiro, o ForkJoinPool é um executor moderno criado para uso com fluxos paralelos do Java 8. A tarefa original era um paralelismo eficiente ao trabalhar com a API Stream, que basicamente significa dividir os fluxos para processar parte dos dados e combiná-los quando todos os dados forem processados. Para simplificar, imagine que você tenha o seguinte código:
IntStream .range(1, 1_000_000) .parallel() .sum()
A quantidade desse fluxo não será calculada em um fluxo. Em vez disso, o ForkJoinPool dividirá recursivamente o intervalo em partes (primeiro em duas partes de 500.000, depois cada uma em 250.000 e assim por diante), calcule a soma de cada parte e combine os resultados em um único quantidade. Aqui está uma visualização desse processo:
Os encadeamentos são divididos para tarefas diferentes e mesclados novamente após a conclusãoA eficácia do FJP é baseada no algoritmo de "roubo de trabalho": quando um encadeamento específico fica sem tarefas, ele vai para as filas de outros encadeamentos de pool e rouba suas tarefas. Para uma melhor compreensão, você pode ver o
relatório de Alexei Shipilev ou assistir a uma
apresentação .
Bem, nós percebemos o que nossas corotinas estão fazendo! Mas como eles acabam lá?
Isso acontece dentro do método de despacho CommonPool #:
_pool.execute(timeSource.trackTask(block))
O método de despacho é chamado no método resume (Value: T) em DispatchedContinuation. Parece familiar! Lembramos que a continuação é uma interface implementada no AbstractCoroutine. Mas como eles estão relacionados?
O truque está dentro da classe CoroutineDispatcher. Ele implementa a interface ContinuationInterceptor da seguinte maneira:
public actual override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(this, continuation)
Está vendo? Você fornece um bloco simples para o construtor corutin. Você não precisa implementar nenhuma interface sobre a qual deseja saber nada. A biblioteca da corotina cuida de tudo isso. Ela é
intercepta a execução, substitui a continuação por DispatchedContinuation e a envia ao executor, o que garante a execução mais eficiente do seu código.
Agora, a única coisa com a qual precisamos lidar é como o despacho é chamado a partir do método start. Vamos preencher essa lacuna. O método resume é chamado de startCoroutine na função de extensão do bloco:
public fun <R, T> (suspend R.() -> T).startCoroutine( receiver: R, completion: Continuation<T> ) { createCoroutineUnchecked(receiver, completion).resume(Unit) }
E startCoroutine, por sua vez, é chamado pelo operador "()" na enumeração CoroutineStart. Seu construtor aceita como o segundo parâmetro e o padrão é CoroutineStart.DEFAULT. Isso é tudo!
Essa é a razão pela qual admiro a abordagem corutin: não é apenas uma sintaxe espetacular, mas também uma implementação brilhante.
E para aqueles que leram até o final, eles são exclusivos: um vídeo do meu relatório “Não é necessário um violinista: recusamos o RxJava em favor da corotina em Kotlin” da conferência Mobius . Enjoy :)