Programação assíncrona: futuros
Conteúdo
O que é importante:
- O código no Dart é executado em uma única execução de thread ( note thread - thread ).
- Devido ao código que retira (bloqueia) o encadeamento por um longo tempo, o programa pode congelar.
- Objetos
futures ( futures ) representam os resultados de operações assíncronas - processamento ou E / S, que serão concluídas posteriormente. - Para suspender a execução para conclusão no futuro, use
await na função assíncrona (ou then() ao usar a API do Future ). - Para detectar erros, use a construção
try-catch (ou catchError() ao usar a API do Future ) na função assíncrona. - Para processamento simultâneo, crie um isolado (ou trabalhador para o aplicativo da web).
O código no Dart é executado em um único thread de execução. Se o código estiver ocupado com cálculos longos ou estiver aguardando uma operação de E / S, o programa inteiro será pausado.
Operações assíncronas permitem que seu programa conclua outras tarefas enquanto aguarda a conclusão da operação. O Dart usa futures para apresentar os resultados de operações assíncronas. Você também pode usar assíncrono e aguardar ou a API do futuro para trabalhar com futures .
Uma nota
Todo o código é executado no contexto do isolado, que possui toda a memória usada pelo código. Mais de uma execução de código não pode ser iniciada no mesmo isolado.
Para execução paralela de blocos de código, você pode separá-los em isolados separados. (Os aplicativos da Web usam trabalhadores em vez de isolados.) Normalmente, cada um dos isolados é executado em seu próprio núcleo do processador. Os isolados não compartilham memória e a única maneira de interagir é enviar mensagens um ao outro. Para mergulhar no tópico, consulte a documentação para isolados ou trabalhadores .
1. Introdução
Vejamos um exemplo de código que pode "congelar" a execução do programa:
Nosso programa lê as notícias do arquivo do dia, exibe-as e exibe informações que ainda são do interesse do usuário:
<gathered news goes here> Winning lotto numbers: [23, 63, 87, 26, 2] Tomorrow's forecast: 70F, sunny. Baseball score: Red Sox 10, Yankees 0
Neste exemplo, o problema é que todas as operações após a chamada gatherNewsReports() aguardarão até o gatherNewsReports() retornar o conteúdo do arquivo, não importa quanto tempo leve. Se a leitura do arquivo demorar muito, o usuário será forçado a aguardar os resultados da loteria, a previsão do tempo e o vencedor de um jogo recente.
Para manter a capacidade de resposta do aplicativo, os autores do Dart usam um modelo assíncrono para identificar funções que executam um trabalho potencialmente caro. Tais funções retornam seu valor usando futures .
Qual é o futuro?
future - uma instância da classe Future <T> , que é uma operação assíncrona que retorna um resultado do tipo T. Se o resultado da operação não for usado, o tipo future será indicado por Future<void> . Ao chamar uma função que retorna future , duas coisas acontecem:
- A função enfileira para execução e retorna um objeto
Future incompleto. - Mais tarde, quando a operação estiver concluída, o
future encerrado com um valor ou erro.
Para escrever um código dependente do future , você tem duas opções:
- Use
async - await - Use a API
Future
Async - aguardar
As palavras-chave async e await fazem parte do suporte async do Dart. Eles permitem escrever código assíncrono que se parece com código síncrono e não usa a API do Future . Uma função assíncrona é uma função com a palavra async chave async na frente do corpo. A palavra-chave await apenas funciona em funções assíncronas.
Nota: no Dart 1.x, funções assíncronas atrasam imediatamente a execução. No Dart 2, em vez de fazer uma pausa imediata, as funções assíncronas são executadas de forma síncrona até a primeira await ou return .
O código a seguir simula a leitura de notícias de um arquivo usando async - await . Abra o DartPad com o aplicativo , inicie e clique em CONSOLE para ver o resultado.
Observe que primeiro chamamos printDailyNewsDigest() , mas as notícias são impressas por último, mesmo que o arquivo contenha apenas uma linha. Isso ocorre porque o código que lê e imprime o arquivo é executado de forma assíncrona.
Neste exemplo, printDailyNewsDigest() faz uma chamada para gatherNewsReports() , que é sem bloqueio. A chamada do método gatherNewsReports() tarefa, mas não impede a execução do restante do código. O programa exibe os números da loteria, previsão e pontuação de um jogo de beisebol; O programa imprime as notícias após a coleta de gatherNewsReports() . Se gatherNewsReports() demorar algum tempo para concluir seu trabalho, nada de ruim acontece: o usuário pode ler outras coisas antes que o resumo diário de notícias seja impresso.
Preste atenção aos tipos de retorno. O tipo de retorno da função gatherNewsReports() é Future<String> , o que significa que retorna um future que termina com um valor de sequência. A função printDailyNewsDigest() , que não retorna um valor, tem um tipo de retorno Future<void> .
O diagrama a seguir mostra as etapas de execução do código.

- O aplicativo começa a ser executado.
- A função
main() é printDailyNewsDigest() função assíncrona printDailyNewsDigest() , que começa a executar de forma síncrona. printDailyNewsDigest() usa await para chamar a função gatherNewsReports() , que começa a executar.gatherNewsReports() retorna um future inacabado (uma instância de Future<String> ).- Como
printDailyNewsDigest() é uma função assíncrona e espera um valor, ele interrompe a execução e retorna o future incompleto (neste caso, Future<void> ) para a função chamadora main () . - O restante das funções de saída são executadas. Como são síncronas, cada função é executada completamente antes de passar para a próxima. Por exemplo, todos os números ganhadores da loteria serão exibidos antes da previsão do tempo.
- Após a conclusão de
main() funções assíncronas podem retomar a execução. Primeiro, obtemos o future com notícias sobre a conclusão de gatherNewsReports() . Em seguida, printDailyNewsDigest() continua a execução, exibindo as notícias. - No final da execução
printDailyNewsDigest() , o future originalmente recebido future e o aplicativo é encerrado.
Observe que a função assíncrona inicia imediatamente (de forma síncrona). A função pausa a execução e retorna um future inacabado quando ocorrer a primeira ocorrência de qualquer um dos seguintes itens:
- A primeira
await expressão (depois que a função obtém o future incompleto dessa expressão). - Qualquer
return em uma função. - O fim do corpo da função.
Tratamento de erros
Provavelmente você gostaria de "capturar" um erro na execução da função que retorna o future . Nas funções assíncronas, você pode manipular erros usando o try-catch :
Future<void> printDailyNewsDigest() async { try { var newsDigest = await gatherNewsReports(); print(newsDigest); } catch (e) {
Um try-catch com código assíncrono se comporta da mesma forma que com código síncrono: se o código no bloco try uma exceção, o código dentro do catch é executado.
Execução sequencial
Você pode usar várias expressões de await para garantir que cada instrução seja concluída antes de executar o seguinte:
expensiveB() função expensiveB() não é executada até que o expensiveA() concluído e assim por diante.
API futura
Antes da adição de async e await no Dart 1.9, era necessário usar a API do Future . Ainda é possível ver o uso da API do Future no código antigo e no código que precisa de mais funcionalidade do que o async–await para oferecer.
Para escrever código assíncrono usando a API do Future , use o método then() para registrar o retorno de chamada. Esse retorno de chamada funcionará quando o future concluído.
O código a seguir simula a leitura de notícias de um arquivo usando a API do Future . Abra o DartPad com o aplicativo , inicie e clique em CONSOLE para ver o resultado.
Observe que primeiro chamamos printDailyNewsDigest() , mas as notícias são impressas por último, mesmo que o arquivo contenha apenas uma linha. Isso ocorre porque o código que lê e imprime o arquivo é executado de forma assíncrona.
Este aplicativo é executado da seguinte maneira:
- O aplicativo começa a ser executado.
- A função principal chama
printDailyNewsDigest() , que não retorna o resultado imediatamente, mas chama primeiro gatherNewsReports() . gatherNewsReports() começa a ler notícias e retorna no future .printDailyNewsDigest() usa then() para registrar um retorno de chamada que terá como parâmetro o valor obtido no final do future . A chamada then() retorna um novo future , que termina com o valor retornado pelo retorno de chamada de then() .- O restante das funções de saída são executadas. Como são síncronas, cada função é executada completamente antes de passar para a próxima. Por exemplo, todos os números ganhadores da loteria serão exibidos antes da previsão do tempo.
- Quando todas as notícias forem recebidas, o
future retornado pela função gatherNewsReports() termina com uma sequência que contém as notícias coletadas. - O código especificado em
then() em printDailyNewsDigest() é executado para printDailyNewsDigest() notícias. - O aplicativo está sendo encerrado.
Nota: na função printDailyNewsDigest() , o código future.then(print) equivalente ao seguinte: future.then((newsDigest) => print(newsDigest)) .
Além disso, o código dentro de then() pode usar chaves:
Future<void> printDailyNewsDigest() { final future = gatherNewsReports(); return future.then((newsDigest) { print(newsDigest);
Você deve especificar o argumento de retorno de chamada em then() , mesmo se future for do tipo Future<void> . Por convenção, um argumento não utilizado é definido por meio de _ (sublinhado).
final future = printDailyNewsDigest(); return future.then((_) {
Tratamento de erros
Usando a API do Future , você pode capturar o erro usando catchError() :
Future<void> printDailyNewsDigest() => gatherNewsReports().then(print).catchError(handleError);
Se a notícia não for legível, o código acima será executado da seguinte maneira:
future retornado por gatherNewsReports() falha.future retornado por then() falha, print() não print() chamado.- O
catchError() em catchError() ( handleError() ) captura o erro, o future retornado por catchError() concluído normalmente e o erro não se propaga mais.
A cadeia catchError() - catchError() é um padrão comum ao usar a API do Future . Considere esse par como o equivalente a um try-catch na API do Future .
Assim como (), catchError () retorna um novo future que termina com o valor de retorno do retorno de chamada. Para mergulhar no tópico, leia Futuros e manipulação de erros .
Chamando várias funções retornando o future
Vamos considerar três funções: expensiveA() , expensiveB() , expensiveC() , que retornam o future . Você pode chamá-los seqüencialmente (uma função inicia após a conclusão da anterior) ou pode executá-los todos ao mesmo tempo e fazer algo assim que todos os valores retornarem. A interface do futuro é flexível o suficiente para implementar os dois casos de uso.
Uma cadeia de chamadas de função usando then()
Quando as funções que retornam o future devem ser executadas em ordem, use a cadeia a partir de then() :
expensiveA() .then((aValue) => expensiveB()) .then((bValue) => expensiveC()) .then((cValue) => doSomethingWith(cValue));
Anexar retornos de chamada também funciona, mas é mais difícil de ler. ( nota http://callbackhell.com/ )
Aguardando a conclusão de vários futures usando Future.wait()
Se a ordem de execução das funções não for importante, você poderá usar Future.wait() . Quando você especifica a lista de futures para parâmetros para a função Future.wait (), ela retorna imediatamente o future . Esse future não terminará até que todos os futures especificados futures . Este future terminará com uma lista dos resultados de todos os futures indicados.
Future.wait([expensiveA(), expensiveB(), expensiveC()]) .then((List responses) => chooseBestResponse(responses, moreInfo)) .catchError(handleError);
Se uma chamada para qualquer uma das funções falhar, o future retornado por Future.wait() também falhará. Use catchError() para capturar esse erro.
O que mais ler?
Dart 2. Programação assíncrona: fluxos de dados