Flutter入门——异步编程

Posted by lingjye on July 15, 2019

异步支持

Dart库中有许多返回Future或Stream对象的类型。这些函数是异步的:它们在设置可能耗时的操作(例如I/O)后返回,而不等待该操作完成。

async和await关键字用来支持异步编程。

处理Futures

当我们需要的结果在将来返回时,有两种实现方式:

  1. 使用async和await关键字。
  2. 使用Future API

使用async和await的异步代码,看起来很像同步代码。例如:

1
await lookUpVersion();

/Users/txooo/Documents/Blogs/_posts/2019-07-15-dart_05.md 如果使用了await,那么必须在函数名后使用async来标记函数为异步函数:

1
2
3
4
5
6
7
Future checkVersion() async {
  await lookUpVersion();
}

Future lookUpVersion() async {
  // ...
}
  • 虽然异步函数可能会执行耗时的操作,但它不会等待这些操作。相反,异步函数只在遇到第一个await表达式时执行。然后它返回一个Future对象,仅在await表达式完成后才恢复执行。

使用try,catch和finally时使用await以:

1
2
3
4
5
6
7
8
9
try {
    // assert(1==2);
    var version = await checkVersion();
    print('version is :$version');
} catch (e) {
	print(e);
} finally {
	print('over');
}

在一个函数中,await可以多次使用。

1
2
3
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

在await表达式中,表达式的值通常发生在将来。如果不是,那么该值将自动包装到Future中。这个Future表示对返回一个对象的承诺。await表达式的值是返回的对象,它会使执行暂停,知道对象可用时恢复。

一定要确保await在一个异步函数中,否则使用await在编译时会抛出错误。即使是在main函数中使用await,也必须要使用async来声明main()是异步的。

1
2
3
4
Future main() async {
  checkVersion();
  print('In main: version is ${await lookUpVersion()}');
}

声明一步函数

一个异步函数是用使用了async关键字标记的普通函数产生的。在一个函数中添加async官架子使其返回一个Future,它还可以用于匿名函数,例如:

1
2
// 匿名函数,返回一个字符串
String syncLookUpVersion() => '1.0.0';

修改为异步函数:

1
Future<String> asyncLpokUpVersion() async => '1.0.0';
  • 函数的主体不需要使用Future API,如有必要,Dart会创建Future对象,如果函数没有返回值,可以将其返回类型设置为Future

处理Stream

当需要从Stream获取值时,也有两种方法:

  1. 使用async和异步for循环(asynchronous for loop)(即await for)
  2. 使用Stream API

使用await for之前,要确保它能使代码更清晰,并且您确实希望等待所有Stream的结果。例如,通常不应该是哟好难过await for去处理UI事件监听,因为UI框架会发送无休止的事件流。

await for形式:

1
2
3
await for (varOrType identifier in expression) {
  // Executes each time the stream emits a value.
}

表达式的值必须具有Stream类型,执行过程如下:

  1. 等待stream发送一个值,
  2. 执行for循环主体,将变量设置为发出的值,
  3. 重复1和2,直到stream关闭。

要终止监听stream,可以使用break或return语句,然后会在for循环断开处取消stream订阅。

同样,await for也需要确保在异步函数中使用。main()也不例外:

1
2
3
4
5
6
7
Future main() async {
  // ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  // ...
}

具体示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Future awaitForExample() async {
  var stream = countStream(10);
  var sum = await sumStream(stream);
  print(sum);
}

// 使用async*和yield生成简单的整数流,下面会介绍
Stream<int> countStream(int number) async* {
  for (int i = 0; i < number; i++) {
    yield i;
  }
}

Future<int> sumStream(Stream<int> stream) async {
  var sum = 0;
  await for (var value in stream) {
    sum += value;
  }
  return sum;
}

生成器

当需要惰性生成一系列值时,可以使用生成器函数。Dart内置了两种生成器:

  1. 同步生成器(Synchronous generator),返回Iterable(可迭代)对象。
  2. 异步生成器(Asynchronous generator),返回Stream(流)对象。

使用sync*和yield来实现同步生成器函数:

1
2
3
4
5
6
Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) {
    yield k++;
  }
}

使用async*和yield来实现异步生成器函数:

1
2
3
4
5
6
Stream<int> asyncNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) {
    yield k++;
  }
}

如果生成器是递归的,可以使用yield*来提高性能:

1
2
3
4
5
6
Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

Isolates

大多数计算机,即使在移动平台上,也有多核CPU。为了充分利用这些核心,开发人一般采用并发运行的共享内存线程。但是,共享状态并发容易出错,并且可能导致代码复杂化。

所有Dart代码都在隔离区内运行,而不是线程。每个隔离区都有自己的内存堆,确保不会从任何其他隔离区访问隔离区的状态。

使用时需要导入:

1
import 'dart:isolate';

Dart是单线程模型,但是使用Isolates可以用于多线程。

Isolates官方文档;

本文Demo