Flutter异步编程: Futures

1,606 阅读9分钟

重点

  • Dart代码运行在一个已执行的线程内。
  • 阻塞线程执行的代码能够使程序冻结。
  • Future对象(Futures)表示异步执行的结果(将要完成的执行结果或I/O操作)。
  • 异步函数使用await()(或者用then()),暂停执行直到future完成。
  • 异步函数使用try-cache表达式,捕获错误。
  • 构造一个isolate(web 使用 worker),立刻运行代码,

Dart代码运行在一个已执行的线程内。如果Dart代码阻塞,例如,运行一个长时间运算或者等待I/O操作,程序将要冻结。 异步操作可以让你在等待一个操作完成时,进行其他工作。Dart使用Future对象(Futures)表示异步执行结果。 你可以使用async和await也可以使用Future API,进行futures开发。

Node: 所有Dart代码运行在isolate上下文中,这个isolate拥有Dart代码所有内存。当Dart代码执行时,isolate内不能运行其他代码。 如果你想多部分Dart代码同时运行,你能运行他们在其他的isolate(Web 用workers代替 isolate)。多个isolate同时运行,通常运行在各自的CPU内核上。isolate不共享内存,他们之间的唯一沟通方式是发送消息。关于isolate更多内容,请查看文档isolates或者web workers

介绍

让我们看一些可能导致程序冻结的代码:

// 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()从文件读取返回值之后执行。如果读取文件花费很长时间,用户必须等,尽管用户可能更想知道他们是否赢了彩票,明天的天气是什么,谁赢了今天的比赛。

为了保证程序响应,Dart库作者处理耗时操作时使用异步模式,这些函数使用future作为返回值。

#Future 是什么? future是一个Future对象,它表示一个异步操作产生一个T类型的结果。如果结果为不可用的值,返回类型为Future。当一个返回futrue的函数被调用时,有两件事发生:

  1. 该函数将加入工作队列,并返回一个未完成的Future对象。
  2. 之后,当操作完成后,返回完成的Future对象值或者错误。

使用future,有两种方式:

  • 用async 和 await
  • 用Future API

Async 和 await

async和await是Dart支持异步编程的一部分。他们允许你写异步代码,看起来像同步代码并且不需要使用Future API。 异步函数即是将async关键字放在函数体前即可。await关键字只用在async函数。 下面程序使用async和await模拟从 www.dartlang.org. 读取内容:

// 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',
//    );

运行结果:

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

printDailyNewsDigest()是第一个被调用的,虽然只是输出一行,但是新闻也是最后被打印的。这是因为运行和打印代码是异步运行的。

在这个例子中,printDailyNewsDigest()调用非阻塞gatherNewsReports(),调用gatherNewsReports()会将执行任务加入队列,但不会阻止剩下代码执行。程序打印彩票号码,预测棒球比赛分数。当gatherNewsReports() 完成获得新闻后,打印它。如果gatherNewsReports()花费一些时间完成,由于异步执行也不会给用户带来很大的影响。在打印每日新闻之前用户可以阅读其他消息。

请留意返回类型, gatherNewsReports()的返回类型是Future,这意味这个返回值是一个以字符future。printDailyNewsDigest() 无返回值,它的返回类型为Future。

下图展示调用流程,数字和步骤相互对应:

  1. 程序开始运行
  2. main函数调用printDailyNewsDigest(),printDailyNewsDigest()开始执行。
  3. printDailyNewsDigest()使用await调用atherNewsReports(),atherNewsReports()开始执行。
  4. gatherNewsReports()返回一个未完成的future(一个Future实例)。
  5. 因为printDailyNewsDigest()是一个异步函数并且等待返回值,它暂停执行并返回一个未完成的future(这种情况下,返回的是Future)给它的调用者(这里是main函数)。
  6. 执行剩下的打印函数。因为他们是同步的,每个函数完全执行完,才会执行下一个函数。例如,彩票中奖号码都是在天气预报之前打印出来的。
  7. 当main()函数完成执行,异步函数仍然能执行。首先gatherNewsReports() 完成后返回future,然后 printDailyNewsDigest()继续执行,打印新闻。
  8. 当printDailyNewsDigest()函数体完成执行,完成的future返回,程序退出。

注意,异步函数立即(同步地)开始执行。当第一次出现一下情况时,函数暂停执行,并返回一个未完成的future:

  • 函数的第一个await表达式(函数获得未完成future之后)。
  • reture 语句。
  • 函数体结束。

错误处理

异步函数使用try-cache进行错误处理。

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

try-cache的行为在异步代码和同步代码中是相同的,如果try块中的代码抛出异常,则catch子句中的代码将执行。

顺序处理

您可以使用多个await表达式来确保顺序执行,即每个语句在执行下一个语句之前完成:

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

expensiveA()执行完成后,才会执行expensiveB().

#Future API 在Dart 1.9中添加async和await之前,您必須使用Future API。目前仍然可能在老的Dart代码中以及需要比asyn-await提供更丰富功能的代码中,看到Future API。

使用Future API写异步代码,用then()注册回调。当Future完成后,回调被执行。

下面程序使用Future API模拟从 www.dartlang.org. 读取内容:

// 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',
//    );

结果输出:

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

printDailyNewsDigest()是第一个被调用的,虽然只是输出一行,但是新闻也是最后被打印的。这是因为运行和打印代码是异步运行的。

程序执行步骤:

  1. 开始执行
  2. main()函数调用printDailyNewsDigest(),printDailyNewsDigest()没有立即返回,而是调用了gatherNewsReports().
  3. gatherNewsReports()开始获取新闻并且返回一个Future。
  4. printDailyNewsDigest()使用then()处理对应的Future返回值。调用then()返回一个新的Future,它将作为then()的回调参数。
  5. 执行剩下的打印函数。因为他们是同步的,每个函数完全执行完,才会执行下一个函数。例如,彩票中奖号码都是在天气预报之前打印出来的。
  6. 当所有的新闻都收到后, gatherNewsReports()完成后返回包含新闻信息字符串的Future。
  7. 在printDailyNewsDigest()中指定的then()执行,打印新闻。
  8. 退出程序。

Node : 在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...
  });
}

你需要提供一个参数给then()的回调,即使Future是Future类型。按照惯例,一个无用参数使用下划线表示。

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. then()返回的future以错误结束,print()函数不被掉用。
  3. catchError() (handleError())处理错误, catchError()返回的正常的future,并且错误不会被传播。

链式模式是Future API的常见模式。可以将Future API 等同于try-catch模块。

与then()类似,catchError()返回一个新的Future,它是回调的返回值。 更多详细信息和例子,请参考Futures and Error Handling.

调用多个函数

考虑三个函数,expensiveA(), expensiveB(), 和expensiveC(),这三个函数都返回Future对象。你能顺序调用他们,或者你能同时开始他们,并在所有函数执行完成后做一些事情。Future接口可以轻松的处理这两种用例。

使用then()进行链式调用

当函数需要按顺序执行时,使用链式then():

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

嵌套回调虽然可以工作,但是难于阅读。

使用Future.wait()等待多个future完成

如果函数的执行顺序不重要,你可以用Future.wait()。

当你传递一个future列表给Future.wait(),它立刻返回一个未完成的future。直到给定的future列表全部执行完这个future才完成。future的返回值,由列表中的每个future的返回值组成。

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

如果任何一个函数返回错误,Future.wait()的futrue都以错误结束。使用catchError()处理错误。

#其他资源 阅读以下文档,了解有关在Dart中使用future和异步编程的更多详细信息:

#原文链接 Asynchronous Programming: Futures