异步编程:期货
目录内容
重要的是:
- Dart中的代码在单个线程( 请注意线程-线程 )执行中运行。
- 由于长时间占用(阻止)线程的代码,程序可能会冻结。
Future ( futures )对象表示异步操作的结果-处理或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应用程序使用工作程序而不是隔离程序。)通常,每个隔离程序都在其自己的处理器核心上运行。 隔离区不共享内存,它们进行交互的唯一方法是相互发送消息。 要深入探讨该主题,请参阅有关隔离或工作人员的文档。
引言
让我们看一个可以“冻结”程序执行的代码示例:
我们的程序从当天的文件中读取新闻,进行显示,然后显示用户仍然感兴趣的信息:
<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返回其价值。
未来会怎样?
future是Future <T>类的实例,该类是一个异步操作,返回类型T的结果。如果未使用该操作的结果,则Future<void>的类型由Future<void>指示。 调用返回future的函数时,会发生两件事:
- 该函数排队执行,并返回不完整的
Future对象。 - 稍后,当操作完成时,
future终止于一个值或错误。
要编写future依赖的代码,您有两个选择:
- 使用
async - await - 使用
Future API
异步-等待
async和await关键字是Dart async 支持的一部分 。 它们使您可以编写看起来像同步代码并且不使用Future API的异步代码。 异步函数是在其主体前面带有async关键字的函数。 await关键字仅在异步函数中起作用。
注意:在Dart 1.x中,异步函数会立即延迟执行。 在Dart 2中,异步函数不是立即暂停,而是同步执行直到第一次await或return 。
以下代码模拟使用async从文件读取新闻。 使用应用程序打开DartPad ,启动并单击控制台以查看结果。
请注意,我们首先调用printDailyNewsDigest() ,但是即使文件仅包含一行,新闻也会最后打印。 这是因为读取和打印文件的代码是异步运行的。
在此示例中, printDailyNewsDigest()调用不阻塞的gatherNewsReports() 。 调用gatherNewsReports()方法gatherNewsReports()作业gatherNewsReports() ,但不会停止其余代码的执行。 该程序显示棒球比赛的彩票号码,预测和得分; 该程序在gatherNewsReports()的收集gatherNewsReports()之后打印新闻。 如果gatherNewsReports()需要一些时间来完成其工作,则不会发生任何不好的情况:用户可以在打印每日新闻摘要之前阅读其他内容。
注意返回类型。 gatherNewsReports()函数的返回类型为Future<String> ,这意味着它将返回以字符串值结尾的Future<String> 。 不返回值的printDailyNewsDigest()函数的返回类型为Future<void> 。
下图显示了代码执行步骤。

- 该应用程序开始运行。
main()函数printDailyNewsDigest()异步函数,该函数开始同步运行。printDailyNewsDigest()使用await来调用gatherNewsReports()函数,该函数开始运行。gatherNewsReports()返回未完成的Future<String> ( Future<String>的实例)。- 由于
printDailyNewsDigest()是一个异步函数并需要一个值,因此它将暂停执行并将未完成的future返回future main ()函数(在本例中为Future<void>的实例)。 - 其余的输出功能将执行。 由于它们是同步的,因此在继续进行下一个功能之前,将完全执行每个功能。 例如,所有中奖彩票号码将在天气预报之前显示。
- 完成
main()异步函数可以恢复执行。 首先,我们获得关于gatherNewsReports()完成的新闻的future 。 然后printDailyNewsDigest()继续执行,显示新闻。 - 在
printDailyNewsDigest()执行结束时,原始接收到的future ,并且应用程序退出。
请注意,异步功能立即启动(同步)。 当以下任何一项的第一次出现时,该函数暂停执行并返回未完成的future :
- 第一个
await表达式(函数从该表达式获取不完整的future )。 - 函数中的任何
return 。 - 函数主体的末尾。
错误处理
您最有可能希望在执行中返回“ future ”的函数中“捕获”错误。 在异步函数中,您可以使用try-catch处理错误:
Future<void> printDailyNewsDigest() async { try { var newsDigest = await gatherNewsReports(); print(newsDigest); } catch (e) {
带有异步代码的try-catch行为与同步代码相同:如果try块中的代码try异常,则执行catch内的代码。
顺序执行
您可以使用多个await表达式来确保每个语句在执行以下操作之前完成:
expensiveB()操作之后,才能执行expensiveB()函数。
未来的API
在Dart 1.9中添加async和await之前,您必须使用Future API。 您仍然可以在旧代码以及需要比async–await功能更多的代码中看到Future API的使用async–await必须提供。
要使用Future API编写异步代码,请使用then()方法注册回调。 future完成时,此回调将起作用。
以下代码模拟使用Future API从文件读取新闻。 使用应用程序打开DartPad ,启动并单击控制台以查看结果。
请注意,我们首先调用printDailyNewsDigest() ,但是即使文件仅包含一行,新闻也会最后打印。 这是因为读取和打印文件的代码是异步运行的。
该应用程序运行如下:
- 该应用程序开始运行。
- 主要函数调用
printDailyNewsDigest() ,该函数不会立即返回结果,而是首先调用gatherNewsReports() 。 gatherNewsReports()开始阅读新闻并返回gatherNewsReports() 。printDailyNewsDigest()使用then()注册一个回调,该回调将采用在future结束时获得的值作为参数。 then()调用返回一个新的future ,其结尾是then()的回调返回的值。- 其余输出功能将执行。 由于它们是同步的,因此在继续进行下一个功能之前,将完全执行每个功能。 例如,所有中奖彩票号码将在天气预报之前显示。
- 接收到所有新闻后,
gatherNewsReports()函数返回的future以包含所收集新闻的字符串结尾。 - 执行
printDailyNewsDigest()中then()中指定的代码以printDailyNewsDigest()新闻。 - 该应用程序正在关闭。
注意:在printDailyNewsDigest()函数中,代码future.then(print)等效于以下内容: future.then((newsDigest) => print(newsDigest)) 。
另外, then()的代码可以使用花括号:
Future<void> printDailyNewsDigest() { final future = gatherNewsReports(); return future.then((newsDigest) { print(newsDigest);
即使future的类型为Future<void> ,也必须在then()指定回调参数。 按照惯例,未使用的参数通过_ (下划线)定义。
final future = printDailyNewsDigest(); return future.then((_) {
错误处理
使用Future API,您可以使用catchError()捕获错误:
Future<void> printDailyNewsDigest() => gatherNewsReports().then(print).catchError(handleError);
如果新闻不可读,则执行以下代码:
gatherNewsReports()返回的future失败。future then()返回的future失败, print()不会调用print() 。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.异步编程:数据流