异步编程:期货
目录内容
重要的是:
- 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.异步编程:数据流