目录

Contents

异步编程:使用 Future 和 async-await

本章的重点

What's the point?

  • Dart代码运行在单个执行“线程”中。

    Dart code runs in a single “thread” of execution.

  • 阻塞执行线程的代码会使你的程序“冻结”。

    Code that blocks the thread of execution can make your program freeze.

  • 一个Future 对象用于表示 异步操作 的结果,这些正在处理的操作或 I/O 将会在稍后完成。

    Future objects (futures) represent the results of asynchronous operations — processing or I/O to be completed later.

  • 在异步函数中使用 await 关键字暂停代码的执行,直到对应的 future 完成。

    To suspend execution until a future completes, use await in an async function.

  • 可以使用 try-catch 表达式来捕获异步函数中代码的执行错误。

    To catch errors, use try-catch expressions in async functions.

  • 为了可以使不同的代码片段同时执行,可以为它们创建一个 isolate(如果你的程序是一个 Web 应用,则创建 worker 替代 isolate

    To run code concurrently, create an isolate (or for a web app, a worker).

Dart 代码运行在单个执行“线程”中。如果 Dart 代码在执行时阻塞,例如:处理一个需要长时间运行的计算操作或等待 I/O 完成。此时整个程序会被“冻结”。

Dart code runs in a single “thread” of execution. If Dart code blocks — for example, by performing a long-running calculation or waiting for I/O — the entire program freezes.

异步操作可以让你的程序在等待一个操作完成时继续处理其它的工作。Dart 使用 Future 对象来表示异步操作的结果。你可以用 asyncawait 关键字或 Future 类的相关 API 来配合使用 future。

Asynchronous operations let your program complete other work while waiting for an operation to finish. Dart uses Future objects (futures) to represent the results of asynchronous operations. To work with futures, you can use either async and await or the Future API.

引言

Introduction

让我们来看一些会使一个程序“冻结”的代码:

Let’s look at some code that might cause a program to freeze:

// Synchronous code
// 同步执行的代码
void printDailyNewsDigest() {
  // var newsDigest = gatherNewsReports(); // Can take a while.
  var newsDigest = gatherNewsReports(); // 执行该函数会耗费一定时间。
  print(newsDigest);
}

main() {
  printDailyNewsDigest();
  printWinningLotteryNumbers();
  printWeatherForecast();
  printBaseballScore();
}

上述的程序中我们搜集当天的新闻以及用户感兴趣的其它一些信息并打印输出到控制台:

Our program gathers the news of the day, prints it, and then prints a bunch of other items of interest to the user:

<gathered news goes here>

<搜集的新闻信息在这(省略……)>

Winning lotto numbers: [23, 63, 87, 26, 2]

乐透中奖号码:[23、63、87、26、2]

Tomorrow's forecast: 70F, sunny.

明天天气预报:21摄氏度,晴。

Baseball score: Red Sox 10, Yankees 0

棒球赛比分:红袜队 10,洋基队 0

上述的代码的问题在于:由于 gatherNewsReports() 函数阻塞,不管要等多久,剩下的代码都会在 gatherNewsReports() 函数返回了文件的内容后才会运行。如果读取文件需要耗费很长的时间,即便用户想马上知道他们是否中了乐透?明天天气如何?以及今天谁赢了比赛?都不得不等待 gatherNewsReports() 函数执行完毕。

Our code is problematic: since gatherNewsReports() blocks, the remaining code runs only after gatherNewsReports() returns with the contents of the file, however long that takes. If reading the file takes a long time, the user has to wait, wondering if they won the lottery, what tomorrow’s weather will be, and who won today’s game.

为了帮助保持应用的响应速度,Dart 库的创作者们在定义可能需要执行耗时操作的函数时使用一种异步模型。这类函数使用一个 future 对象返回它们的值。

To help keep the application responsive, Dart library authors use an asynchronous model when defining functions that do potentially expensive work. Such functions return their value using a future.

future是什么?

What is a future?

future 是 Future<T> 类的对象,其表示一个 T 类型的异步操作结果。如果异步操作不需要结果,则 future 的类型可为 Future<void>。当一个返回 future 对象的函数被调用时,会发生两件事:

A future is a Future<T> object, which represents an asynchronous operation that produces a result of type T. If the result isn’t a usable value, then the future’s type is Future<void>. When a function that returns a future is invoked, two things happen:

  1. 将函数操作列入队列等待执行并返回一个未完成的 Future 对象。

    The function queues up work to be done and returns an uncompleted Future object.

  2. 不久后当函数操作执行完成,Future 对象变为完成并携带一个值或一个错误。

    Later, when the operation is finished, the Future object completes with a value or with an error.

当你写的代码依赖于 future 对象时,你有两种可选的实现方式:

When writing code that depends on a future, you have two options:

  • 使用关键字 asyncawait

    Use async and await

  • 使用 Future API

    Use the Future API

关键字 async 和 await

Async and await

关键字 asyncawait 是 Dart 语言 异步支持 的一部分。它们允许你不使用 Future 的 API 编写看起来与同步代码一样的异步代码。异步函数 即在函数头中包含关键字 async 的函数。关键字 await 只能用在异步函数中。

The async and await keywords are part of the Dart language’s asynchrony support. They allow you to write asynchronous code that looks like synchronous code and doesn’t use the Future API. An async function is one that has the async keyword before its body. The await keyword works only in async functions.

接下来的应用使用关键字 asyncawait 读取本网站上文件的内容来模拟读取新闻。点击运行按钮开始运行该应用。或者打开一个 包含该应用的 DartPad 窗口, 来运行该应用,请点击 CONSOLE 查看应用的运行结果输出。

The following app simulates reading the news by using async and await to read the contents of a file on this site. Click run to start the app. Or open a DartPad window containing the app, run the app, and click CONSOLE to see the app’s output.

这里需要注意,虽然函数 printDailyNewsDigest() 是第一个被调用的函数,但是新闻信息却是最后才打印输出的,即便该新闻信息只是一行模拟文本。这是因为代码在读取和打印输出该信息时是异步的。

Notice that printDailyNewsDigest() is the first function called, but the news is the last thing to print, even though the file contains only a single line. This is because the code that reads and prints the file is running asynchronously.

在这个例子中,函数 printDailyNewsDigest() 调用函数 gatherNewsReports()的过程是非阻塞的。调用函数 gatherNewsReports() 时会将该函数操作列入队列执行但不会阻止其它代码的执行。程序打印输出乐透号码、天气、以及棒球赛比分;当 gatherNewsReports() 函数搜集完新闻后程序也将其打印输出。即使 gatherNewsReports() 函数需要花费一点时间来完成它的工作,这也不会造成太大的影响:用户可以在每日新闻摘要打印输出前查看其它信息。

In this example, the printDailyNewsDigest() function calls gatherNewsReports(), which is non-blocking. Calling gatherNewsReports() queues up the work to be done but doesn’t stop the rest of the code from executing. The program prints the lottery numbers, the forecast, and the baseball score; when gatherNewsReports() finishes gathering news, the program prints. If gatherNewsReports() takes a little while to complete its work, no great harm is done: the user gets to read other things before the daily news digest is printed.

注意函数的返回类型。函数 gatherNewsReports() 的返回类型是 Future<String>,这表示其返回一个完成时包含一个字符串值的 future 对象。而函数 printDailyNewsDigest() 因为本身没有返回值,所以在变为异步函数后其返回值类型为 Future<void>

Note the return types. The return type of gatherNewsReports() is Future<String>, which means that it returns a future that completes with a string value. The printDailyNewsDigest() function, which doesn’t return a value, has the return type Future<void>.

下面的图示展示了代码的执行流程。每个数字对应图示下面的一个步骤。

The following diagram shows the flow of execution through the code. Each number corresponds to a step below.

diagram showing flow of control through the main() and printDailyNewsDigest functions

  1. 应用开始执行。

    The app begins executing.

  2. main() 函数开始以同步的方式执行并调用异步函数 printDailyNewsDigest()

    The main() function calls the async function printDailyNewsDigest(), which begins executing synchronously.

  3. printDailyNewsDigest()函数开始执行,并使用关键字 await 调用 gatherNewsReports() 函数。

    printDailyNewsDigest() uses await to call the function gatherNewsReports(), which begins executing.

  4. 函数 gatherNewsReports() 返回一个未完成的 future 对象( Future<String> 类的一个实例)。

    The gatherNewsReports() function returns an uncompleted future (an instance of Future<String>).

  5. 因为函数 printDailyNewsDigest() 是一个异步函数并且它在等待一个值(该值由第 4 步中函数 gatherNewsReports() 返回的 future 对象提供),所以它暂停其函数内代码的执行并返回一个未完成的 future 对象(在此例中,该 future 对象为 Future<void> 的一个实例)给它的调用者(main() 函数)。

    Because printDailyNewsDigest() is an async function and is awaiting a value, it pauses its execution and returns an uncompleted future (in this case, an instance of Future<void>) to its caller (main()).

  6. 执行其它的打印输出函数。因为这些函数是同步的,所以每一个函数完整地执行完后才会执行下一个函数。例如乐透的中奖号码一定会在天气预报之前打印出来。

    The remaining print functions execute. Because they’re synchronous, each function executes fully before moving on to the next print function. For example, the winning lottery numbers are all printed before the weather forecast is printed.

  7. main() 函数完成执行时,异步函数将会恢复执行。首先,函数 gatherNewsReports() 返回的 future 对象完成。然后函数 printDailyNewsDigest() 继续执行,最后打印出新闻。

    When main() has finished executing, the asynchronous functions can resume execution. First, the future returned by gatherNewsReports() completes. Then printDailyNewsDigest() continues executing, printing the news.

  8. printDailyNewsDigest() 函数完成执行时,其刚开始返回的 future 对象也完成,并且应用退出。

    When the printDailyNewsDigest() function body finishes executing, the future that it originally returned completes, and the app exits.

请注意异步函数是立即开始执行的(同步地),其将会在下述情况之一首次出现时暂停执行并返回一个未完成的 future 对象:

Note that an async function starts executing right away (synchronously). The function suspends execution and returns an uncompleted future when it reaches the first occurrence of any of the following:

  • 函数中第一个 await 表达式出现时(在该函数从 await 表达式获取到未完成的 future 之后)。

    The function’s first await expression (after the function gets the uncompleted future from that expression).

  • 函数中任何 return 语句的出现时。

    Any return statement in the function.

  • 函数体的结束。

    The end of the function body.

错误处理

Handling errors

如果一个 Future 在函数返回完成时有错误,你可能想要捕获该错误。异步函数中可以使用 try-catch 语句来处理错误:

If a Future-returning function completes with an error, you probably want to capture that error. Async functions can handle errors using try-catch:

Future<void> printDailyNewsDigest() async {
  try {
    var newsDigest = await gatherNewsReports();
    print(newsDigest);
  } catch (e) {
    // Handle error...
    // 处理代码执行错误...
  }
}

try-catch 语句处理异步代码错误的方式与同步代码相同: catch 语句块中的代码会在 try 语句块中的代码抛出异常时执行。

The try-catch code behaves in the same way with asynchronous code as it does with synchronous code: if the code within the try block throws an exception, the code inside the catch clause executes.

顺序处理

Sequential processing

你可以使用多个 await 表达式来确保各个语句在执行下一个语句之前完成:

You can use multiple await expressions to ensure that each statement completes before executing the next statement:

// Sequential processing using async and await.
// 使用关键字 async 和 await 顺序处理异步函数逻辑
main() async {
  await expensiveA();
  await expensiveB();
  doSomethingWith(await expensiveC());
}

函数 expensiveB() 将会在函数 expensiveA() 执行完毕后才执行,接下来的其它类似函数也如此。

The expensiveB() function doesn’t execute until expensiveA() has finished, and so on.

其它资源信息

Other resources

可以阅读下面的文档获取更多关于在 Dart 中使用 future 和异步编程的信息:

Read the following documentation for more details on using futures and asynchronous programming in Dart:

接下来做什么?

What next?

  • Read the streams教程, which shows you how to work with an event stream.

    查阅 streams教程,可以获取更多关于如何使用事件流的文档。