Executando processos paralelos com Flutter/DART

toshiossada

Toshi Ossada

Posted on April 26, 2024

Executando processos paralelos com Flutter/DART

Executando processos paralelos com Flutter/DART

O Flutter que conhecemos tem uma execução num único thread e essa thread é executada em único, o que chamamos, de isolate.

Essa isolate, conhecida como main isolate, e as vezes necessitamos executar processos que podem gerar concorrência então a main isolate faz toda a gerência de seu event loop para que possamos executar códigos de forma assíncrona.

Então quer dizer que se eu tiver uma tarefa que executa muito tempo basta colocar no event loop que não terei problema? errado! há casos que temos tarefas muito pesados que causara gargalo em meu event loop (que também contém instruções de renderização) e irá causar travamentos (janks) na tela.

No exemplo acima temos uma animação que roda constantemente, mas perceba-se que quando executamos uma função, no caso o cálculo de Fibonacci, que é um cálculo custoso.

Nesse caso a solução seria fazer um processamento paralelo ao main isolate criando um novo isolate (já que Future, event loops, etc NÃO É processamento paralelo).

Mas o que são isolates?

O dart utiliza isolates que executam suas instruções de forma sequencial e em uma região da memória dedicada a ela. Cada isolate tem sua memória dedicada e seu próprio event loop que são isolados para esse propósito. diferente das threads esses isolates não podem acessar os dados um dos outros diretamente. Toda comunicação deve ser feita através de mensagens.

Afim de prever alguns erros comuns na utilização de threads como: mutabilidade de dados, os clássicos problemas de bloqueios, dead-locks, etc. que são bastante comuns em programação paralela.

Para trabalharmos para criar isolates existem basicamente duas formas, a primeira forma mais simples é utilizando o método compute().

O método compute() recebe 2 parâmetros, o primeiro é um método(NOTE QUE ESSE MÉTODO NECESSITA SER STATIC) que será executado dentro da isolate que será criado e o outro é o parâmetro que irá passar da main isolate para a nova isolate.

Ao final da execução da isolate ela irá retornar um valor para a mais isolate, não há necessidade de preocupar com o fechamento da isolate e não há comunicação por mensagens entre a isolate criada e a main isolate.

A segunda forma mais completa é utilizando o Isolate.spawn(), este também possui dois parâmetros igualmente o compute().

Nesse caso criamos um Objeto chamado Message que iremos utilizar como parâmetro do método a ser executado dentro da isolate, criamos o ReceiverPort que é uma abstração que implementa uma stream através dele conseguimos passar o sendPort para a nova isolate. Através deles conseguimos fazer a comunicação entre as duas isolates, onde as mensagens enviadas pelo sendPort são capturadas no ReceivePort e como trata de uma Stream, podemos escutar essas mensagens.

O método Isolate.exit() faz a transferência de dados para o Isolate receptor, que nesse exemplo seria o main.

Algumas vantagens na utilização de isolates são:

A possibilidade de execução de operações computacionais custosas de forma performática;
Possibilita a experiencia fluida de animações.

Então agora vou utilizar isolates a rodo em meu projeto? Não, nem tudo são flores, então existem desvantagens de utilizar isolates, como:

Complexidade de código;
É um processo extremamente custoso ao dispositivo, principalmente em questão de memória.

Então nosso exemplo do Fibonacci ficaria da seguinte forma.

Note que não há mais problemas de janks na execução do calculo.

Agora imagine o caso em que eu preciso que você tem um aplicativo aplicando todos os bons conceitos do SOLID e utiliza um package de service locator para resolver as dependências de sua classe?

Você recebe a usecase por construtor da controller.

E tenta executar o caso de uso dentro da isolate.

Isso não será possível simplesmente pelo fato de que a inicialização dos Binds (no caso do Modular) terem sido feito na memória da main isolate e como não há compartilhamento de memoria entre os isolates. Uma solução é fazer a inicialização dos Binds novamente do package que escolheu para service locator.

No caso do Flutter Modular podemos criar um modulo isolado contendo todas as instancias de classe que iremos utilizar dentro do isolate.

e através do Modular.init() podemos inicializar dentro da isolate o modulo novo.

Dessa forma conseguimos recuperar as instancias através do Modular.get().

Legal né? Muito simples fazer programaçao paralela no Dart, tem um exemplo muito legal no meu github, entre lá confira e dar estrelinha no repositório.

https://github.com/toshiossada/hive_example

Image description

Entre em nosso discord para interagir com a comunidade: https://discord.com/invite/flutterbrasil
https://linktr.ee/flutterbrasil

💖 💪 🙅 🚩
toshiossada
Toshi Ossada

Posted on April 26, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related