Dardo 2. Programação assíncrona: futuros

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:


 // Synchronous code void printDailyNewsDigest() { var newsDigest = gatherNewsReports(); // Can take a while. print(newsDigest); } main() { printDailyNewsDigest(); printWinningLotteryNumbers(); printWeatherForecast(); printBaseballScore(); } 

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:


  1. A função enfileira para execução e retorna um objeto Future incompleto.
  2. 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.


Código de exemplo
 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'dart:async'; Future<void> printDailyNewsDigest() async { var newsDigest = await gatherNewsReports(); print(newsDigest); } main() { printDailyNewsDigest(); printWinningLotteryNumbers(); printWeatherForecast(); printBaseballScore(); } printWinningLotteryNumbers() { print('Winning lotto numbers: [23, 63, 87, 26, 2]'); } printWeatherForecast() { print("Tomorrow's forecast: 70F, sunny."); } printBaseballScore() { print('Baseball score: Red Sox 10, Yankees 0'); } const news = '<gathered news goes here>'; const oneSecond = Duration(seconds: 1); // Imagine that this function is more complex and slow. :) Future<String> gatherNewsReports() => Future.delayed(oneSecond, () => news); // Alternatively, you can get news from a server using features // from either dart:io or dart:html. For example: // // import 'dart:html'; // // Future<String> gatherNewsReportsFromServer() => HttpRequest.getString( // 'https://www.dartlang.org/f/dailyNewsDigest.txt', // ); 

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.



  1. O aplicativo começa a ser executado.
  2. A função main() é printDailyNewsDigest() função assíncrona printDailyNewsDigest() , que começa a executar de forma síncrona.
  3. printDailyNewsDigest() usa await para chamar a função gatherNewsReports() , que começa a executar.
  4. gatherNewsReports() retorna um future inacabado (uma instância de Future<String> ).
  5. 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 () .
  6. 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.
  7. 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.
  8. 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) { // Handle error... } } 

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:


 // Sequential processing using async and await. main() async { await expensiveA(); await expensiveB(); doSomethingWith(await expensiveC()); } 

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.


Código de exemplo
 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'dart:async'; Future<void> printDailyNewsDigest() { final future = gatherNewsReports(); return future.then(print); // You don't *have* to return the future here. // But if you don't, callers can't await it. } main() { printDailyNewsDigest(); printWinningLotteryNumbers(); printWeatherForecast(); printBaseballScore(); } printWinningLotteryNumbers() { print('Winning lotto numbers: [23, 63, 87, 26, 2]'); } printWeatherForecast() { print("Tomorrow's forecast: 70F, sunny."); } printBaseballScore() { print('Baseball score: Red Sox 10, Yankees 0'); } const news = '<gathered news goes here>'; const oneSecond = Duration(seconds: 1); // Imagine that this function is more complex and slow. :) Future<String> gatherNewsReports() => Future.delayed(oneSecond, () => news); // Alternatively, you can get news from a server using features // from either dart:io or dart:html. For example: // // import 'dart:html'; // // Future<String> gatherNewsReportsFromServer() => HttpRequest.getString( // 'https://www.dartlang.org/f/dailyNewsDigest.txt', // ); 

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:


  1. O aplicativo começa a ser executado.
  2. A função principal chama printDailyNewsDigest() , que não retorna o resultado imediatamente, mas chama primeiro gatherNewsReports() .
  3. gatherNewsReports() começa a ler notícias e retorna no future .
  4. 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() .
  5. 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.
  6. 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.
  7. O código especificado em then() em printDailyNewsDigest() é executado para printDailyNewsDigest() notícias.
  8. 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); // Do something else... }); } 

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((_) { // Code that doesn't use the `_` parameter... print('All reports printed.'); }); 

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:


  1. future retornado por gatherNewsReports() falha.
  2. future retornado por then() falha, print() não print() chamado.
  3. 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

Source: https://habr.com/ru/post/pt442282/


All Articles