Dart 2.异步编程:期货

异步编程:期货


目录内容



重要的是:


  • Dart中的代码在单个线程( 请注意线程-线程 )执行中运行。
  • 由于长时间占用(阻止)线程的代码,程序可能会冻结。
  • Futurefutures )对象表示异步操作的结果-处理或I / O,将在以后完成。
  • 要在将来暂停执行直至完成,请在异步函数中使用await (或在使用Future API时, then()使用then() )。
  • 要捕获错误,请在异步函数中使用try-catch构造(或使用Future API时使用catchError() )。
  • 为了同时进行处理,请创建一个隔离(或Web应用程序的工作程序)。

Dart中的代码在单个执行线程中运行。 如果代码忙于长时间计算或正在等待I / O操作,则整个程序将暂停。


异步操作允许您的程序在等待操作完成的同时完成其他任务。 Dart使用futures来呈现异步操作的结果。 您还可以使用async and await或Future API与futures


笔记


所有代码都是在代码所使用的所有内存所属的隔离环境中执行的。 不能在同一隔离中启动多个代码执行。

对于并行执行代码块,可以将它们分成单独的隔离区。 (Web应用程序使用工作程序而不是隔离程序。)通常,每个隔离程序都在其自己的处理器核心上运行。 隔离区不共享内存,它们进行交互的唯一方法是相互发送消息。 要深入探讨该主题,请参阅有关隔离工作人员的文档。

引言


让我们看一个可以“冻结”程序执行的代码示例:


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

我们的程序从当天的文件中读取新闻,进行显示,然后显示用户仍然感兴趣的信息:


 <gathered news goes here> Winning lotto numbers: [23, 63, 87, 26, 2] Tomorrow's forecast: 70F, sunny. Baseball score: Red Sox 10, Yankees 0 

在此示例中,问题在于,调用gatherNewsReports()之后的所有操作都将一直等到gatherNewsReports()返回文件的内容, gatherNewsReports()花费多长时间。 如果读取文件需要很长时间,则将迫使用户等待彩票,天气预报和近期游戏获胜者的结果。


为了保持应用程序的响应能力,Dart作者使用异步模型来识别执行潜在昂贵工作的功能。 这些函数使用futures返回其价值。


未来会怎样?


futureFuture <T>类的实例,该类是一个异步操作,返回类型T的结果。如果未使用该操作的结果,则Future<void>的类型由Future<void>指示。 调用返回future的函数时,会发生两件事:


  1. 该函数排队执行,并返回不完整的Future对象。
  2. 稍后,当操作完成时, future终止于一个值或错误。

要编写future依赖的代码,您有两个选择:


  • 使用async - await
  • 使用Future API

异步-等待


asyncawait关键字是Dart async 支持的一部分 。 它们使您可以编写看起来像同步代码并且不使用Future API的异步代码。 异步函数是在其主体前面带有async关键字的函数。 await关键字仅在异步函数中起作用。


注意:在Dart 1.x中,异步函数会立即延迟执行。 在Dart 2中,异步函数不是立即暂停,而是同步执行直到第一次awaitreturn

以下代码模拟使用async从文件读取新闻。 使用应用程序打开DartPad ,启动并单击控制台以查看结果。


范例程式码
 // 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', // ); 

请注意,我们首先调用printDailyNewsDigest() ,但是即使文件仅包含一行,新闻也会最后打印。 这是因为读取和打印文件的代码是异步运行的。


在此示例中, printDailyNewsDigest()调用不阻塞的gatherNewsReports() 。 调用gatherNewsReports()方法gatherNewsReports()作业gatherNewsReports() ,但不会停止其余代码的执行。 该程序显示棒球比赛的彩票号码,预测和得分; 该程序在gatherNewsReports()的收集gatherNewsReports()之后打印新闻。 如果gatherNewsReports()需要一些时间来完成其工作,则不会发生任何不好的情况:用户可以在打印每日新闻摘要之前阅读其他内容。


注意返回类型。 gatherNewsReports()函数的返回类型为Future<String> ,这意味着它将返回以字符串值结尾的Future<String> 。 不返回值的printDailyNewsDigest()函数的返回类型为Future<void>


下图显示了代码执行步骤。



  1. 该应用程序开始运行。
  2. main()函数printDailyNewsDigest()异步函数,该函数开始同步运行。
  3. printDailyNewsDigest()使用await来调用gatherNewsReports()函数,该函数开始运行。
  4. gatherNewsReports()返回未完成的Future<String>Future<String>的实例)。
  5. 由于printDailyNewsDigest()是一个异步函数并需要一个值,因此它将暂停执行并将未完成的future返回future main ()函数(在本例中为Future<void>的实例)。
  6. 其余的输出功能将执行。 由于它们是同步的,因此在继续进行下一个功能之前,将完全执行每个功能。 例如,所有中奖彩票号码将在天气预报之前显示。
  7. 完成main()异步函数可以恢复执行。 首先,我们获得关于gatherNewsReports()完成的新闻的future 。 然后printDailyNewsDigest()继续执行,显示新闻。
  8. printDailyNewsDigest()执行结束时,原始接收到的future ,并且应用程序退出。

请注意,异步功能立即启动(同步)。 当以下任何一项的第一次出现时,该函数暂停执行并返回未完成的future


  • 第一个await表达式(函数从该表达式获取不完整的future )。
  • 函数中的任何return
  • 函数主体的末尾。

错误处理


您最有可能希望在执行中返回“ future ”的函数中“捕获”错误。 在异步函数中,您可以使用try-catch处理错误:


 Future<void> printDailyNewsDigest() async { try { var newsDigest = await gatherNewsReports(); print(newsDigest); } catch (e) { // Handle error... } } 

带有异步代码的try-catch行为与同步代码相同:如果try块中的代码try异常,则执行catch内的代码。


顺序执行


您可以使用多个await表达式来确保每个语句在执行以下操作之前完成:


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

expensiveB()操作之后,才能执行expensiveB()函数。


未来的API


在Dart 1.9中添加asyncawait之前,您必须使用Future API。 您仍然可以在旧代码以及需要比async–await功能更多的代码中看到Future API的使用async–await必须提供。


要使用Future API编写异步代码,请使用then()方法注册回调。 future完成时,此回调将起作用。


以下代码模拟使用Future API从文件读取新闻。 使用应用程序打开DartPad ,启动并单击控制台以查看结果。


范例程式码
 // 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', // ); 

请注意,我们首先调用printDailyNewsDigest() ,但是即使文件仅包含一行,新闻也会最后打印。 这是因为读取和打印文件的代码是异步运行的。


该应用程序运行如下:


  1. 该应用程序开始运行。
  2. 主要函数调用printDailyNewsDigest() ,该函数不会立即返回结果,而是首先调用gatherNewsReports()
  3. gatherNewsReports()开始阅读新闻并返回gatherNewsReports()
  4. printDailyNewsDigest()使用then()注册一个回调,该回调将采用在future结束时获得的值作为参数。 then()调用返回一个新的future ,其结尾是then()的回调返回的值。
  5. 其余输出功能将执行。 由于它们是同步的,因此在继续进行下一个功能之前,将完全执行每个功能。 例如,所有中奖彩票号码将在天气预报之前显示。
  6. 接收到所有新闻后, gatherNewsReports()函数返回的future以包含所收集新闻的字符串结尾。
  7. 执行printDailyNewsDigest()then()中指定的代码以printDailyNewsDigest()新闻。
  8. 该应用程序正在关闭。

注意:在printDailyNewsDigest()函数中,代码future.then(print)等效于以下内容: future.then((newsDigest) => print(newsDigest))

另外, then()的代码可以使用花括号:


 Future<void> printDailyNewsDigest() { final future = gatherNewsReports(); return future.then((newsDigest) { print(newsDigest); // Do something else... }); } 

即使future的类型为Future<void> ,也必须在then()指定回调参数。 按照惯例,未使用的参数通过_ (下划线)定义。


 final future = printDailyNewsDigest(); return future.then((_) { // Code that doesn't use the `_` parameter... print('All reports printed.'); }); 

错误处理


使用Future API,您可以使用catchError()捕获错误:


 Future<void> printDailyNewsDigest() => gatherNewsReports().then(print).catchError(handleError); 

如果新闻不可读,则执行以下代码:


  1. gatherNewsReports()返回的future失败。
  2. future then()返回的future失败, print()不会调用print()
  3. catchError()handleError() )中的catchError()捕获该错误, catchError()返回的future正常完成,并且该错误不会进一步传播。

then()catchError()是使用Future API时的常见模式。 将此对视为Future API中的try-catch的等效项。

像then()一样,catchError()返回一个新的Future,以回调的返回值结尾。 要深入探讨该主题,请阅读期货和错误处理


调用多个函数返回future


让我们考虑三个函数: expensiveA()expensiveB()expensiveC() ,它们返回future 。 您可以依次调用它们(一个函数在上一个函数完成后启动),也可以同时运行它们,并在所有值返回后立即执行操作。 Future的界面足够灵活,可以实现两个用例。


使用then()的函数调用链
当返回future的函数必须按顺序执行时,请使用then()的链:


 expensiveA() .then((aValue) => expensiveB()) .then((bValue) => expensiveC()) .then((cValue) => doSomethingWith(cValue)); 

附加回调也可以,但是很难阅读。 ( 请注意http://callbackhell.com/


使用Future.wait()等待多个futures完成
如果函数的执行顺序不重要,则可以使用Future.wait() 。 当为Future.wait()函数的参数指定futures列表时,它将立即返回future 。 在所有指定的future futures之前,这个future不会结束。 该future将以所有指示的futures的结果列表结尾。


 Future.wait([expensiveA(), expensiveB(), expensiveC()]) .then((List responses) => chooseBestResponse(responses, moreInfo)) .catchError(handleError); 

如果对任何函数的调用失败,则Future.wait()返回的Future.wait()也将失败。 使用catchError()捕获此错误。




还有什么要读的?


Dart 2.异步编程:数据流

Source: https://habr.com/ru/post/zh-CN442282/


All Articles