# Dart教程 - 12 异步

# 12.1 异步和同步

什么是异步和同步?

举个栗子:

查看下面的代码,在 main 函数中调用 requestNetwork 函数获取网络数据。

import "dart:io";
import 'dart:convert';

main() {
  print("main start");
  print(requestNetwork());
  print("main end");
}

String requestNetwork() {
  sleep(Duration(seconds: 3)); // 休眠3秒,模拟从网络获取数据

  Map<String, dynamic> data = {
    'name': '逗比',
    'age': 25,
  };

  String jsonString = jsonEncode(data);    // 将字典转换为json
  return jsonString;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

main 函数执行到 print(requestNetwork()) 的时候,会卡在这里3秒,因为 requestNetwork 函数中会休眠3秒,直到 requestNetwork 函数执行完成,返回结果, main 函数才会继续执行后续的代码。

代码按照顺序一步一步执行,每一步都会等待前一步完成后再进行,如果遇到耗时的操作,程序会阻塞等待,直到前一步执行完成,才会继续执行后续代码的方式,这种方式叫做 同步同步 意味着如果有耗时操作,程序就会阻塞。

如果 main 函数执行到 print(requestNetwork()) 的时候,不阻塞等待 requestNetwork 函数执行完成,就继续执行后面的代码,也就是存在main 函数和 requestNetwork 函数两个任务同时执行,这种方式叫做 异步异步 是非阻塞的。

在实际的开发中如何处理耗时的操作呢?

在开发中,会遇到网络请求,文件读写等耗时的操作,这些操作是不能放在主线程中进行的,因为会阻塞主线程,导致程序卡住,用户无法与程序进行交互。

在Java或C++等程序中,是通过开启子线程,将耗时的操作放在子线程中进行处理,子线程和主线程同时运行,实现异步操作,子线程处理完成,在将结果交给主线程。

但是Dart默认是单线程的,该如何进行异步操作呢?

# 12.2 Dart中的异步

Dart 应用程序默认情况下是单线程的,如何进行异步操作呢,乍一听,确实让人黑人问号。

其实一个应用程序大部分时间都是处于空闲的状态的,比如等待网络请求数据的返回、文件读写的IO操作,这些等待的行为并不会阻塞我们的线程,所以类似于网络请求、文件读写的IO,我们都可以基于非阻塞调用;

举个生活中的栗子:

到饭点了,点个外卖,点外卖就是调用函数,外卖就是函数返回的结果。

如果是阻塞调用的方式,则点完外卖就在傻等,直到拿到外卖,然后才可以干后面的其他事情。

如果是非阻塞调用的方式,则点完外卖就可以去干其他的事情,只需要定时去门口看看外面到了没有就行了。

Dart中是使用 单线程 + 事件循环 来实现异步操作的。

在单线程模型维护着一个事件循环(Event Loop),当有网络请求、文件读写IO操作等事件时,会将这些事件加入到事件队列中,事件循环会不断的从事件队列中取出事件并处理,直到事件队列清空。

通过Dart代码如何实现呢?

在Dart代码中,异步操作主要使用 Future以及 asyncawait 来实现。

# 12.3 Future

# 1 Future的用法

现在回头看看一开始同步执行的代码:

import "dart:io";
import 'dart:convert';

main() {
  print("main start");
  print(requestNetwork());
  print("main end");
}

String requestNetwork() {
  sleep(Duration(seconds: 3)); // 休眠3秒,模拟从网络获取数据

  Map<String, dynamic> data = {
    'name': '逗比',
    'age': 25,
  };

  String jsonString = jsonEncode(data);
  return jsonString;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

执行结果:

main start
{"name":"逗比","age":25}
main end
1
2
3

上面的代码是同步执行的,如何修改为异步的操作呢?

我们先修改代码如下:

import "dart:io";
import 'dart:convert';

main() {
  print("main start");
  print(requestNetwork());
  print("main end");
}

Future<String> requestNetwork() {
  return new Future(() {
    sleep(Duration(seconds: 3)); // 休眠3秒,模拟从网络获取数据

    Map<String, dynamic> data = {
      'name': '逗比',
      'age': 25,
    };

    String jsonString = jsonEncode(data);
    return jsonString;
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

创建了一个 Future 对象,然后将耗时的操作放在一个函数中传给了 Future

执行结果:

main start
Instance of 'Future<String>'
main end
1
2
3

在执行的时候,会发现很快执行完成,没有看到阻塞的现象,这是因为使用了 Future 对象将耗时的操作隔离了起来,不会影响主线程。

但是打印的请求网络的结果是一个 Future 对象,并不是最终的结果,那么如何获取最终的结果呢?

通过 .then方法 回调获取异步结果。

修改代码如下:

import "dart:io";
import 'dart:convert';

main() {
  print("main start");
  
  var future = requestNetwork();
  future.then((value) {
    print("请求结果:$value");
  });

  print("main end");
}

Future<String> requestNetwork() {
  return new Future(() {
    sleep(Duration(seconds: 3)); // 休眠3秒,模拟从网络获取数据

    Map<String, dynamic> data = {
      'name': '逗比',
      'age': 25,
    };

    String jsonString = jsonEncode(data);
    return jsonString;
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

requestNetwork() 函数返回一个 Future对象 ,通过 Future 对象.then方法 可以获取请求的结果。

执行结果:

main start
main end
请求结果:{"name":"逗比","age":25}
1
2
3

如果请求的过程中出现异常,如何捕获异常呢?

# 2 异常捕获

可以使用 .catchError 来捕获异常,代码如下:

import "dart:io";

main() {
  print("main start");

  var future = requestNetwork();
  future.then((value) {
    print("请求结果:$value");
  }).catchError((error) {
    print("捕获异常:$error");
  });

  print("main end");
}

Future<String> requestNetwork() {
  return new Future(() {
    sleep(Duration(seconds: 3)); // 休眠3秒,模拟从网络获取数据

    throw Exception("网络请求出现错误");
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

可以在 .then() 方法后,使用链式调用,继续通过 .catchError() 方法捕获异常。

执行结果:

main start
main end
捕获异常:Exception: 网络请求出现错误
1
2
3

总结:

  • 创建一个Future对象,将异步操作通过函数传给 Future对象
  • 通过 Future对象.then 方法可以获取到事件执行完成的结果;
  • 通过 .catchError 方法可以监听 Future 内部执行失败或者出现异常时的错误信息;

# 3 Future的链式调用

我们可以在 then 中继续返回值,会在下一个链式的 then 调用回调函数中拿到返回的结果。

举个栗子:

修改前面的代码:

import 'dart:convert';

void main() async {
  print("main start");

  fetchPersonInfo();

  print("main end");
}

void fetchPersonInfo() {
  var info = requestNetwork();
  print(info);
}

Future<String> requestNetwork() {
  var result = Future.delayed(Duration(seconds: 3), () {
    Map<String, dynamic> data = {
      'name': '逗比',
      'age': 25,
    };

    String jsonString = jsonEncode(data);
    return jsonString;
  });

  return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

我们在使用 future 对象的第一个 then 中拿到请求的结果,然后可以将结果 return,return 的结果可以被下一个 then 接收。

执行结果:

main start
main end
请求结果:{"name":"逗比","age":25}
请求结果:逗比
1
2
3
4

链式调用这个有什么用呢?

链式调用可以解决 回调地狱

什么是回调地狱?

回调地狱(Callback Hell)是指在异步编程中,多个异步操作嵌套在一起,形成多层的回调函数,使得代码看起来非常复杂、难以维护和理解的情况。在回调地狱中,每个异步操作都需要在前一个异步操作完成后进行,并且依赖于前一个异步操作的结果。

举个栗子:

我们有四个异步的方法,加、减、乘、除,我们在调用减法的时候需要拿到加法的结果参与运算,调用乘法的时候需要拿到减法的结果参与运算,调用除法的时候需要拿到乘法的结果参与运算,于是编写代码如下:

import "dart:io";

Future<int> add(int a, int b) {
  return new Future(() {
    sleep(Duration(seconds: 3));
    return a + b;
  });
}

Future<int> subtract(int a, int b) {
  return new Future(() {
    sleep(Duration(seconds: 3));
    return a - b;
  });
}

Future<int> multiply(int a, int b) {
  return new Future(() {
    sleep(Duration(seconds: 3));
    return a * b;
  });
}

Future<double> divide(int a, int b) {
  return new Future(() {
    sleep(Duration(seconds: 3));
    return a / b;
  });
}

void main() async {
  print("main start");

  add(6, 4).then((value) {
    print("加法的结果:$value");

    subtract(value, 4).then((value) {
      print("减法的结果:$value");

      multiply(value, 2).then((value) {
        print("乘法的结果:$value");

        divide(value, 4).then((value) {
          print("除法的结果:$value");
        });
      });
    });
  });

  print("main end");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

通过上面的代码可以看出,我们通过 then 获取到异步结果,然后在 then 中继续发起异步请求,在得到结果后又发起了异步请求,then 层层嵌套,这就是回调地狱,很难维护。

通过 链式调用,可以解决回调地狱,将嵌套改为串联的方式。

修改代码如下:

import "dart:io";

Future<int> add(int a, int b) {
  return new Future(() {
    sleep(Duration(seconds: 3));
    return a + b;
  });
}

Future<int> subtract(int a, int b) {
  return new Future(() {
    sleep(Duration(seconds: 3));
    return a - b;
  });
}

Future<int> multiply(int a, int b) {
  return new Future(() {
    sleep(Duration(seconds: 3));
    return a * b;
  });
}

Future<double> divide(int a, int b) {
  return new Future(() {
    sleep(Duration(seconds: 3));
    return a / b;
  });
}

void main() async {
  print("main start");

  add(6, 4).then((value) {
    print("加法的结果:$value");
    return subtract(value, 4);
  }).then((value) {
    print("减法的结果:$value");
    return multiply(value, 2);
  }).then((value) {
    print("乘法的结果:$value");
    return divide(value, 4);
  }).then((value) {
    print("除法的结果:$value");
  }).catchError((error) {
    print("Error: $error"); // 捕获异常
  });
  ;

  print("main end");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

上面的代码使用了 链式调用,在.then()中获取到运算的结果,然后返回一个新的 Future对象进行下一步的运算操作,直到获取到最终的结果,并通过.catchError()捕获可能出现的异常。

# 4 Future的其他方法

# Future.value()

Future.value(value)是用于创建一个立即完成,并且结果为指定值的Future对象的方法。这个方法可以在不涉及异步操作的情况下创建一个Future,使其立即完成,并将指定的值作为结果返回。

举个栗子:

main() {
  print("main start");

  var future = Future.value(42);
  future.then((value) {
    print(value);
  });

  print("main end");
}
1
2
3
4
5
6
7
8
9
10

上面使用 Future.value(42) 创建了一个 Future 对象,并没有异步的代码,没有涉及异步操作,结果将立即返回,由 then 接收。

执行结果:

main start
main end
42
1
2
3

Future.value(42) 创建的是一个立即完成的 Future,为什么 42 最后打印出来呢?

这是因为Future中的then会作为新的任务会加入到事件队列中,等到事件循环处理到它时再执行。

上面的操作让人和疑惑,这有什么作用,有点脱裤子放屁,我直接 print(42) 得了呗,有什么使用场景呢?

  • 统一接口: 在某些情况下,可能希望将异步和同步操作都通过 Future 的方式进行处理,以保持代码的一致性。当您有一个函数需要返回Future类型的结果,而实际上该结果是同步操作的结果时,那么就可以使用 Future.value(value) 将同步操作的结果包装为立即完成的Future,以便统一返回类型的要求。

  • 模拟: 在单元测试中,有时候可能需要模拟一个返回 Future 的函数,以便测试异步操作的情况。我们可以使用Future.value(value)来创建一个模拟的Future对象,以返回预定的结果。

  • 函数式编程: 在函数式编程风格中,有时候可能会使用 Future 作为一种容器,用于将异步和同步操作一致地处理。在这种情况下,您可以使用Future.value(value)来包装同步操作的结果,以便将其纳入函数式编程的范畴。

用到了自然会理会,现在可不用那么理会。

# Future.error()

Future.error(object) 可以用于创建一个立即完成,但是结果为错误对象的 Future 的方法,该Future会直接调用 catchError的回调函数。

举个栗子:

main() {
  print("main start");

  var future = Future.error(Exception("错误信息"));
  future.catchError((error) {
    print(error);
  });

  print("main end");
}
1
2
3
4
5
6
7
8
9
10

上面使用 Future.error(object)创建了一个立即完成的 Future,并创建了一个异常对象作为参数。我们通过 .catchError() 方法来捕获这个 Future 的错误,并在回调函数中输出错误信息。

执行结果:

main start
main end
Exception: 错误信息
1
2
3

Future.error(object) 有什么使用场景呢?

  • 错误模拟: 在测试环境中,可能希望模拟一个特定的错误情况,以便测试代码对错误的处理方式。通过使用 Future.error(object)可以立即创建一个错误的 Future 对象,以便测试错误处理的逻辑。

  • 同步异常包装: 在某些情况下,可能会有一个同步操作抛出异常,但您仍然想将这个异常包装在一个 Future 中,以便在处理代码中统一处理异常。通过 Future.error(object),您可以将同步异常包装为一个立即完成的 Future,使其可以通过 .catchError()async/await 机制进行处理。

# Future.delayed()

Future.delayed(时间, 回调函数) 可以在延迟一定时间时执行回调函数,执行完回调函数后会执行then的回调。

我们一开始讲解的例子就可以使用 Future.delayed() 来编写,只是一上来讲它容易懵逼。

import "dart:convert";

main() {
  print("main start");

  var future = requestNetwork();
  future.then((value) {
    print("请求结果:$value");
  });

  print("main end");
}

Future<String> requestNetwork() {
  return Future.delayed(Duration(seconds: 3), () {
    Map<String, dynamic> data = {
      'name': '逗比',
      'age': 25,
    };

    String jsonString = jsonEncode(data);
    return jsonString;
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

使用 Future.delayed() 可以模拟异步操作的延迟,例如在从网络获取数据或执行其他耗时操作之前的等待时间。它也可以用于实现定时器,延迟执行某些代码,或者在特定时间段后触发事件等。

# 12.4 await、async

await、async是什么?

await、async 可以让我们用 同步的代码格式,去实现 异步的调用过程

我们先从简单的代码入手,渐进明细:

import 'dart:convert';

void main() async {
  print("main start");

  fetchPersonInfo();

  print("main end");
}

void fetchPersonInfo() {
  var info = requestNetwork();
  print("info:$info");
}

Future<String> requestNetwork() {
  var future = Future.delayed(Duration(seconds: 3), () {
    Map<String, dynamic> data = {
      'name': '逗比',
      'age': 25,
    };

    String jsonString = jsonEncode(data);
    return jsonString;
  });

  return future;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

代码结构很简单, main() 方法调用了 fetchPersonInfo() 方法,fetchPersonInfo() 方法调用了 requestNetwork() 方法。

执行结果:

main start
info:Instance of 'Future<String>'
main end
1
2
3

因为 Future 的执行是异步的,所以 requestNetwork 方法得到的是一个Future对象,print(info) 打印的结果是 Instance of 'Future<String>'

我们可以使用 await 等待异步的结果,得到结果再返回,修改 requestNetwork() 函数如下:

Future<String> requestNetwork() async {
  var result = await Future.delayed(Duration(seconds: 3), () {
    Map<String, dynamic> data = {
      'name': '逗比',
      'age': 25,
    };

    String jsonString = jsonEncode(data);
    return jsonString;
  });
  
  print("result:$result");

  return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Future.delayed 是异步的,在前面添加 await 表示等待 Future 的执行结果。在异步的操作前面加上 await,代码会报错,需要在方法的后面添加 async。因为 await 必须在 async 方法中使用。

代码执行到 await 的时候会等待,直到返回结果,才会执行后面的代码。

此时的完整代码:

import 'dart:convert';

void main() async {
  print("main start");

  fetchPersonInfo();

  print("main end");
}

void fetchPersonInfo() {
  var info = requestNetwork();
  print("info:$info");
}

Future<String> requestNetwork() async {
  var result = await Future.delayed(Duration(seconds: 3), () {
    Map<String, dynamic> data = {
      'name': '逗比',
      'age': 25,
    };

    String jsonString = jsonEncode(data);
    return jsonString;
  });

  print("result:$result");

  return result;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

执行结果:

main start
info:Instance of 'Future&lt;String&gt;'
main end
result:{"name":"逗比","age":25}
1
2
3
4

执行的结果发现,在 fetchPersonInfo() 函数中调用 requestNetwork() 并没有拿到最终的结果,info变量 还是一个Future对象。

这是因为代码执行到 await 会立即返回一个future 对象,也就是赋给 info变量 的对象。而在 requestNetwork() 函数中继续等待 Future 的执行,执行完成再返回最终的结果。

那么在 fetchPersonInfo() 函数中怎么拿到最终的结果呢?

此时 fetchPersonInfo() 调用 requestNetwork() 还是异步的,我们只需要在 fetchPersonInfo() 中等待 requestNetwork() 执行结果就行,在调用 requestNetwork() 的前面也加上 await

代码如下:

import 'dart:convert';

void main() async {
  print("main start");

  fetchPersonInfo();

  print("main end");
}

void fetchPersonInfo() async {
  var info = await requestNetwork();
  print("info:$info");
}

Future<String> requestNetwork() async {
  var result = await Future.delayed(Duration(seconds: 3), () {
    Map<String, dynamic> data = {
      'name': '逗比',
      'age': 25,
    };

    String jsonString = jsonEncode(data);
    return jsonString;
  });

  print("result:$result");

  return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

执行结果:

main start
main end
result:{"name":"逗比","age":25}
info:{"name":"逗比","age":25}
1
2
3
4

此时可以看到,在 fetchPersonInfo() 方法中拿到了 requestNetwork() 函数的结果。而且采用的是同步调用的方式,而且并没有阻塞main函数 主线程的执行。

总结一下:

  • 在异步操作的前面可以添加 await 关键字,会等待异步操作执行的结果;
  • await 只能在async方法中使用,所以方法要添加async关键字;
  • 代码执行到 await 会立即返回一个future 对象给调用者,然后等待异步执行,执行完成,才会执行 await 后面的代码,最终得到异步的结果返回。
  • 代码用了 同步的代码格式,去实现 异步的调用过程

我们再举一个例子加深印象:

这次我们真的发起一个 httpget 请求:

import 'package:http/http.dart' as http;

main() {
  print("main start");
  getWebsiteStatus();
  print("main end");
}

Future getWebsiteStatus() async {
  print("开始获取网站的状态");

  int status = await getHttp();
  if (status == 200) {			// 200表示可用
    print("网站可用");
  } else {
    print("网站不可用");
  }
}

Future<int> getHttp() async {
  try {
    final result = await http
        .get(Uri.http('www.doubibiji.com', '/', {}))
        .timeout(Duration(seconds: 5));     // 设置请求超时时间为5秒
    // 获取的状态码
    return result.statusCode;
  } catch (e) {
    print("请求出错: $e");
    return -1;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

执行结果:

main start
开始获取网站的状态
main end
网站可用
1
2
3
4

# 12.5 微任务队列

前面讲到事件循环(Event Loop)里面存在一个事件队列(Event Queue),事件循环不断从事件队列中取出事件执行。

但如果严格来划分的话,在Dart中还存在另一个队列:微任务队列(Microtask Queue)。微任务队列的优先级要高于事件队列,也就是说 事件循环 会优先执行 微任务队列 中的任务,再执行 事件队列 中的任务。

下面演示一下如何创建微任务:

import "dart:async";

main() {
  print("main start");

  scheduleMicrotask(() {
    print("你好,逗比");
  });

  print("main end");
}
1
2
3
4
5
6
7
8
9
10
11

scheduleMicrotask() 参数的匿名函数会被加入到微任务队列中等待执行。

执行结果:

main start
main end
你好,逗比
1
2
3

我们将 微任务队列事件队列 做一个对比:

import "dart:async";

main() {
  print("main start");

  Future(() {
    print("会被加入到事件队列中");
  }).then((value) {
    print("执行then");
  });

  scheduleMicrotask(() {
    print("你好,逗比");
  });

  print("main end");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

执行结果:

main start
main end
你好,逗比
会被加入到事件队列中
执行then
1
2
3
4
5

可以看到 Future 在微任务队列之后才被执行,这是因为 Future 构造函数传入的函数体放在事件队列中被执行的。

then 注册的回调函数会被添加到微任务队列中,以确保它们在当前任务完成后尽快执行,而不会受到事件队列中的其他任务的延迟影响。虽然 then 方法的执行不会直接被添加到事件队列,但在某些情况下,可能会触发与事件队列相关的操作。例如,如果在 then 注册的回调函数中执行了某些异步操作,那么这些异步操作可能会在事件队列中注册并执行。但是,then 本身注册的回调函数仍然会被添加到微任务队列中。

# 12.6 多线程

在 Dart 中,默认是单线程的,而每个线程实际上是运行在一个 Isolate 中的。每个 Isolate 都有自己独立的执行线程和事件循环,以及私有的内存空间。

而一个 Isolate 只能利用一个 CPU 内核。所以Dart默认单线程的情况下,只能利用一个 CPU 内核,这对于多核CPU来说,是一种资源的浪费。

如果希望在多个 CPU 内核上并行执行多个任务,我们可以创建多个 Isolate 。每个 Isolate 都是独立的执行单元,可以在一个单独的 CPU 内核上运行。通过创建多个 Isolate,Dart 可以更好地利用多核处理器,提高应用程序的性能。

如果某个操作计算量非常大,以至于它在主线程的 Isolate 运行中会导致阻塞,导致掉帧,我们就可以创建独立的 Isolate 来做密集计算,让主 Isolate 专注重建和渲染。

# 1 创建Isolate

我们可以通过 Isolate.spawn 创建 Isolate

举个栗子:

import "dart:isolate";

main() {
  Isolate.spawn(foo, "你好,逗比");
}

void foo(info) {
  print("新的isolate:$info");
}
1
2
3
4
5
6
7
8
9

上面的代码中使用了 Isolate.spawn 创建了一个新的 Isolate ,并指定了一个函数作为参数,并给函数传递了参数。程序会在新的 Isolate 中执行。

执行结果:

新的isolate:你好,逗比
1

需要注意:各个 Isolate 之间是独立的执行单元,它们之间的数据是相互隔离的,而且不会直接共享内存。

所以看一下下面的代码:

import 'dart:isolate';

int a = 0;

void main() async {
  print('main start');

  // 创建一个 Isolate
  Isolate.spawn(computeAdd, 10);

  print(a);

  print('main end');
}

void computeAdd(int n) {
  int result = 0;
  for (int i = 1; i <= n; i++) {
    result += i;
  }

  a = result;
  print('1到$n的和: $result');
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

因为 int a = 0; 是在主 Isolate 中定义的一个变量,然后在另一个 Isolate 中的 computeAdd 函数中修改了 a 的值。但由于 Isolate 之间没有直接共享内存,这种方式是不会影响主 Isolate 中的 a 值的。因此,在打印 a 时看到的结果仍然是初始值 0,而不是计算后的结果。

执行结果:

main start
1到10的和: 55
0
main end
1
2
3
4

再看一个例子:

import 'dart:isolate';

void main() async {
  print('main start');

  // 创建一个 Isolate
  Isolate.spawn(computeAdd, 10000000);

  print('main end');
}

void computeAdd(int n) {
  int result = 0;
  for (int i = 1; i <= n; i++) {
    result += i;
  }
  print('1到$n的和: $result');
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

执行结果:

main start
main end
1
2

没有输出计算结果 ,这是为什么呢?

因为上面计算需要的时间较长,导致主线程已经执行完成了,导致整个 Dart 程序结束,从而终止所有的 Isolate。

那么应该怎么办呢?

这里就需要用到 Isolate 的通讯机制,可以使用 ReceivePort 来让主线程等待从其他 Isolate 发送的消息,以保持主 Isolate 的运行状态

# 2 Isolate通讯

我们可以在主线程中,将 Main Isolate 的发送管道(SendPort)传递给新创建的Isolate ,当 Isolate 执行完毕时,可以利用这个管道给 Main Isolate 发送消息。

举个栗子:

import 'dart:isolate';

void main() async {
  print('main start');

  final receivePort = ReceivePort();
  
  // 传递多个参数的时候可以使用字典
  Isolate.spawn(
      computeAdd, {'n': 1000000000, 'sendPort': receivePort.sendPort});

  receivePort.listen((result) {
    print('计算结果: $result');
    // 不再使用时,我们会关闭管道
    receivePort.close();
  });

  print('main end');
}

// 传递多个参数的时候可以使用字典
void computeAdd(Map<String, dynamic> args) {
  final n = args['n'];
  final sendPort = args['sendPort'];

  int result = 0;
  for (int i = 1; i <= n; i++) {
    result += i;
  }

  sendPort.send(result);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

在上面的代码中我们首先创建了一个 ReceivePort, 然后将 sendPort 传递给了新创建的 Isolate,如果想传递多个参数给新创建的 Isolate 可以使用字典。然后使用 ReceivePort 开启监听,监听来自新创建 Isolate 的消息。在新创建的 Isolate 中,可以使用 sendPort 发送消息给主 Isolate,如果主 Isolate 不想接受消息了,可以使用 receivePort.close(); 关闭管道。在管道关闭之前,新创建的Isolate 是可以一直给主 Isolate 发送消息的,只是上面只是发送了结果给主 Isolate