https://www.dartcn.com/guides/language/language-tour#%E5%BC%82%E6%AD%A5%E6%94%AF%E6%8C%81
https://www.dartcn.com/guides/libraries/library-tour#future

js-异步编程:https://www.yuque.com/zhuchaoyang/wrif6k/zmuxrv

dart 异步编程

因为dart是单线程的语言,所以如果线程中顺序执行的时候遇到一些耗时阻塞的操作,比如数据请求,延时操作等,就会产生卡顿,所以用异步来解决。

  1. //导入io库,调用sleep函数
  2. import 'dart:io';
  3. import 'dart:async';
  4. // 模拟耗时操作,调用sleep,睡眠2秒。
  5. doTask() {
  6. sleep(Duration(seconds: 2)); //睡眠2秒钟
  7. print('do completed!');
  8. }
  9. main() {
  10. print('begin');
  11. doTask();
  12. print('end');
  13. }
  14. // begin
  15. // 2秒后执行
  16. // do completed!
  17. // end
  18. //后续代码就被阻塞了,必须等异步完成,才能继续执行。

编程中的代码执行,通常分为同步异步两种。简单说,同步就是按照代码的编写顺序,从上到下依次执行,这也是最简单的我们最常接触的一种形式。但是同步代码的缺点也显而易见,如果其中某一行或几行代码非常耗时,那么就会阻塞,使得后面的代码不能被立刻执行。

异步的出现正是为了解决这种问题,它可以使某部分耗时代码不在当前这条执行线路上立刻执行,那究竟怎么执行呢?最常见的一种方案是使用多线程,也就相当于开辟另一条执行线,然后让耗时代码在另一条执行线上运行,这样两条执行线并列,耗时代码自然也就不能阻塞主执行线上的代码了。

多线程虽然好用,但是在大量并发时,仍然存在两个较大的缺陷,一个是开辟线程比较耗费资源,线程开多了机器吃不消,另一个则是线程的锁问题,多个线程操作共享内存时需要加锁,复杂情况下的锁竞争不仅会降低性能,还可能造成死锁。因此又出现了基于事件的异步模型。简单说就是在某个单线程中存在一个事件循环和一个事件队列,事件循环不断的从事件队列中取出事件来执行,这里的事件就好比是一段代码,每当遇到耗时的事件时,事件循环不会停下来等待结果,它会跳过耗时事件,继续执行其后的事件。当不耗时的事件都完成了,再来查看耗时事件的结果。因此,耗时事件不会阻塞整个事件循环,这让它后面的事件也会有机会得到执行。

我们很容易发现,这种基于事件的异步模型,只适合I/O密集型的耗时操作,因为I/O耗时操作,往往是把时间浪费在等待对方传送数据或者返回结果,因此这种异步模型往往用于网络服务器并发。如果是计算密集型的操作,则应当尽可能利用处理器的多核,实现并行计算。

Future - 图1

Dart的事件循环

Dart 是事件驱动的体系结构,该结构基于具有单个事件循环和两个队列的单线程执行模型。 Dart虽然提供调用堆栈。 但是它使用事件在生产者和消费者之间传输上下文。 事件循环由单个线程支持,因此根本不需要同步和锁定。

Dart 的两个队列分别是

  • MicroTask queue 微任务队列
  • Event queue 事件队列

Future - 图2

Dart事件循环执行如上图所示
  1. 先查看MicroTask队列是否为空,不是则先执行MicroTask队列
  2. 一个MicroTask执行完后,检查有没有下一个MicroTask,直到MicroTask队列为空,才去执行Event队列
  3. Evnet 队列取出一个事件处理完后,再次返回第一步,去检查MicroTask队列是否为空

我们可以看出,将任务加入到MicroTask中可以被尽快执行,但也需要注意,当事件循环在处理MicroTask队列时,Event队列会被卡住,应用程序无法处理鼠标单击、I/O消息等等事件。

调度任务

注意,以下调用的方法,都定义在dart:async库中。

将任务添加到MicroTask队列有两种方法

  1. import 'dart:async';
  2. void myTask() {
  3. print("this is my task");
  4. }
  5. void main() {
  6. // 1. 使用 scheduleMicrotask 方法添加
  7. scheduleMicrotask(myTask);
  8. // 2. 使用Future对象添加
  9. new Future.microtask(myTask);
  10. }

将任务添加到Event队列

  1. import 'dart:async';
  2. void myTask() {
  3. print("this is my task");
  4. }
  5. void main() {
  6. new Future(myTask);
  7. }

示例:

  1. import 'dart:async';
  2. void main() {
  3. print(1);
  4. new Future(() {
  5. print(2);
  6. });
  7. new Future.microtask(() {
  8. print(3);
  9. });
  10. print(4);
  11. }
  12. // 1 4 3 2

**

延时任务

如需要将任务延时执行,则可使用Future.delayed方法。

  1. //表示在延迟时间到了之后将任务加入到Event队列。
  2. new Future.delayed(new Duration(seconds: 1), (){
  3. print('task delayed');
  4. });

注意,这并不是准确的,万一前面有很耗时的任务,那么你的延迟任务不一定能准时运行。

  1. import 'dart:async';
  2. import 'dart:io';
  3. void main() {
  4. print("main start");
  5. new Future.delayed(new Duration(seconds: 1), () {
  6. print('task delayed');
  7. });
  8. new Future(() {
  9. // 模拟耗时5秒, io的sleep方法
  10. sleep(Duration(seconds: 5));
  11. print("5s task");
  12. });
  13. print("main stop");
  14. }
  15. //main start
  16. //main stop
  17. //5秒之后打印:
  18. //5s task
  19. //task delayed

从结果可以看出,delayed方法调用在前面,但是它显然并未直接将任务加入Event队列,而是需要等待1秒之后才会去将任务加入,但在这1秒之间,后面的new Future代码直接将一个耗时任务加入到了Event队列,这就直接导致写在前面的delayed任务在1秒后只能被加入到耗时任务之后,只有当前面耗时任务完成后,它才有机会得到执行。这种机制使得延迟任务变得不太可靠,你无法确定延迟任务到底在延迟多久之后被执行。

Future

在 Dart 库中随处可见 Future 对象,通常异步函数返回的对象就是一个 Future。 当一个 future 完成执行后,future 中的值就已经可以使用了。

Future类是对未来结果的一个代理,它返回的并不是被调用的任务的返回值。

  1. void myTask() {
  2. print("this is my task");
  3. }
  4. void main() {
  5. Future fut = new Future(myTask);
  6. }
  7. //Future类实例fut并不是函数myTask的返回值,它只是代理了myTask函数,封装了该任务的执行状态。

处理Future

当你需要完成Future的结果时,你有两个选择:

  • 使用async和await
  • 使用 Future API

创建 Future

Future的几种创建方法

  • Future() 宏任务
  • Future.microtask() 微任务
  • Future.sync() 同步
  • Future.value() 类似Promise.resolve()
  • Future.delayed() 延时
  • Future.error() 类似Promise.reject()

示例:在main函数中,我们调用了getAJoke方法,他返回Future<String>.我们通过调用then方法订阅Future,在then中注册回调函数,当Future返回值时调用注册函数。同时注册了catchError方法处理在Future执行之间发生的异常。

  1. import 'dart:async';
  2. void main() {
  3. // 假设getSome是一个异步请求
  4. getSome() {
  5. return 'this is a joke';
  6. }
  7. // 此处 getAJoke() 就是未来的结果
  8. Future<String> getAJoke() {
  9. return new Future(getSome); //Future的构造函数,需要一个函数作为参数
  10. }
  11. // 未来的结果获取,使用then
  12. getAJoke().then((res) {
  13. print('res => ${res}');
  14. }).catchError((err) {
  15. print('err => ${err}');
  16. });
  17. // res => this is a joke
  18. }

sync是同步方法,任务会被立即执行

  1. import 'dart:async';
  2. void main() {
  3. print(1);
  4. new Future.sync(() {
  5. print(2);
  6. });
  7. new Future(() {
  8. print(3);
  9. });
  10. print(4);
  11. }
  12. // 1 2 4 3

注册回调

Future中的任务完成后,我们往往需要一个回调,这个回调立即执行,不会被添加到事件队列。

then注册回调

  1. import 'dart:async';
  2. void main() {
  3. print("main start");
  4. Future fut = new Future.value(18);
  5. // 使用then注册回调
  6. fut.then((val) {
  7. print('value => ${val}');
  8. });
  9. // 链式调用,可以跟多个then,注册多个回调
  10. new Future(() {
  11. print("async task");
  12. }).then((res) {
  13. print("async task complete");
  14. }).then((res) {
  15. print("async task after");
  16. });
  17. print("main stop");
  18. }
  19. // main start
  20. // main stop
  21. // value => 18
  22. // async task
  23. // async task complete
  24. // async task after

catchError 异常回调

除了then方法,还可以使用catchError来处理异常,如下:

  1. import 'dart:async';
  2. void main() {
  3. new Future(() {
  4. throw new Exception('异常'); //抛出一个异常
  5. }).then((res) {
  6. print('res => ${res}');
  7. }).catchError((err) {
  8. print('error => ${err}');
  9. });
  10. }
  11. // error => Exception: 异常

链式调用

多个网络请求,后者需要前者的返回值当作参数,最后一个才是想要的值。

  1. import 'dart:async';
  2. void main() {
  3. Future<int> f1() async {
  4. return 1;
  5. }
  6. Future<int> f2(int p) async {
  7. return p + 2;
  8. }
  9. Future<int> f3(p) async {
  10. return p + 3;
  11. }
  12. test() {
  13. f1().then((val) => f2(val))
  14. .then((val) => f3(val))
  15. .then((val) {
  16. print(val);
  17. });
  18. }
  19. test(); //6
  20. }

Future.wait 并行执行

同时执行多个网络请求,等所有结果都返回后再执行操作,返回一个List的结果集。

  1. import 'dart:async';
  2. void main() {
  3. print("main start");
  4. Future task1 = new Future(() {
  5. print("task 1");
  6. return 1;
  7. });
  8. Future task2 = new Future(() {
  9. print("task 2");
  10. return 2;
  11. });
  12. Future task3 = new Future(() {
  13. print("task 3");
  14. return 3;
  15. });
  16. Future fut = Future.wait([task1, task2, task3]);
  17. fut.then((responses) {
  18. print('responses => ${responses}');
  19. });
  20. print("main stop");
  21. }
  22. // wait返回一个新的Future,当添加的所有Future完成时,在新的Future注册的回调将被执行。
  23. // main start
  24. // main stop
  25. // task 1
  26. // task 2
  27. // task 3
  28. // responses => [1, 2, 3]

async 和 await

在Dart1.9中加入了asyncawait关键字,有了这两个关键字,我们可以更简洁的编写异步代码,而不需要调用Future相关的API。

async 关键字作为方法声明的后缀时,具有如下意义

  • 被修饰的方法会将一个 Future 对象作为返回值
  • 该方法会同步执行其中的方法的代码直到第一个 await 关键字,然后它暂停该方法其他部分的执行;
  • 一旦由 await 关键字引用的 Future 任务执行完成,await的下一行代码将立即执行。
  1. // 导入io库,调用sleep函数
  2. import 'dart:io';
  3. // 模拟耗时操作,调用sleep函数睡眠2秒
  4. doTask() async {
  5. print('task begin');
  6. await sleep(const Duration(seconds: 2));
  7. return "task end";
  8. }
  9. // 定义一个函数用于包装
  10. test() async {
  11. var r = await doTask();
  12. print(r);
  13. }
  14. void main() {
  15. print("main start");
  16. test();
  17. print("main end");
  18. }
  19. // main start
  20. // task begin
  21. // 2秒后输出:
  22. // main end
  23. // task end

使用 async 和 await 处理 Future
  1. import 'dart:async';
  2. void main() {
  3. Future<String> getAJoke() {
  4. print(3);
  5. return new Future.delayed(new Duration(seconds: 2), () {
  6. return 'this is a joke';
  7. });
  8. }
  9. print(1);
  10. test() async {
  11. var aa = await getAJoke();
  12. print('test => ${aa}');
  13. }
  14. test();
  15. print(2);
  16. }
  17. // 1
  18. // 3
  19. // 2
  20. //2秒后输出:
  21. // test => this is a joke

注意,async 不是并行执行,它是遵循Dart 事件循环规则来执行的,它仅仅是一个语法糖,简化Future API的使用。

执行顺序

如上所示,在调用函数之后,我们添加了print语句。在这种场景中,print语句会先执行,之后future的返回值才会打印。这是future的预期行为.但是如果我们希望在执行其他语句之前,先执行future。所以我们需要用到async/await.

  • 使用new Future将任务加入event队列。
  • Future中的then并没有创建新的Event丢到Event Queue中,而只是一个普通的Function Call,在FutureTask执行完后,立即开始执行。
  • 如果在then()调用之前Future就已经执行完毕了,那么任务会被加入到microtask队列中,并且该任务会执行then()中注册的回调函数。
  • 使用Future.value构造函数的时候,就会上一条一样,创建Task丢到microtask Queue中执行then传入的函数。
  • Future.sync构造函数执行了它传入的函数之后,也会立即创建Task丢到microtask Queue中执行。
  • 当任务需要延迟执行时,可以使用new Future.delay()来将任务延迟执行。

示例:

  1. void main() {
  2. test() {
  3. var f = new Future(() => print('f1'));
  4. var f1 = Future(() => null);
  5. // var f1 = Future.delayed(Duration(seconds: 1), () => null);
  6. var f2 = Future(() => null);
  7. var f3 = Future(() => null);
  8. f3.then((_) => print('f2'));
  9. f2.then((_) {
  10. print('f3');
  11. Future(() => print('f4'));
  12. f1.then((_) {
  13. print('f5');
  14. });
  15. });
  16. f1.then((m) {
  17. print('f6');
  18. });
  19. print('f7');
  20. }
  21. test();
  22. }
  23. // f7
  24. // f1
  25. // f6
  26. // f3
  27. // f5
  28. // f2
  29. // f4
  1. import 'dart:async';
  2. void main() {
  3. test() {
  4. scheduleMicrotask(() => print('s1'));
  5. new Future.delayed(new Duration(seconds: 1),
  6. () => print('s2')); //因为有延时所以具体执行时间不一定,时间长就最后,时间很短可能在s8后打印
  7. new Future(() => print('s3')).then((_) {
  8. print('s4');
  9. scheduleMicrotask(() => print('s5')); //需要等整个future结束后再去判断Microtask是否有任务
  10. }).then((_) => print('s6'));
  11. new Future(() => print('s7'));
  12. scheduleMicrotask(() => print('s8'));
  13. print('s9');
  14. }
  15. test();
  16. }
  17. //打印
  18. // s9
  19. // s1
  20. // s8
  21. // s3
  22. // s4
  23. // s6
  24. // s5
  25. // s7
  26. // s2
  1. import 'dart:async';
  2. main(List<String> args) async {
  3. // async wait
  4. getName1();
  5. getName2();
  6. getName3();
  7. }
  8. // async wait
  9. Future<void> getName1() async {
  10. print('ssss'); //可以不用await打断点看下await后的区别
  11. await getStr1(); //遇到第一个await表达式执行暂停,返回future对象,await表达式执行完成后继续执行
  12. await getStr2(); //await表达式可以使用多次
  13. print('getName1');
  14. }
  15. getStr1() {print('getStr1');}
  16. getStr2() {print('getStr2');}
  17. getName2() {print('getName2');}
  18. getName3() {print('getName3');}
  19. //打印
  20. // ssss
  21. // getStr1
  22. // getName2
  23. // getName3
  24. // getStr2
  25. // getName1

Isolate

前面已经说过,将非常耗时的任务添加到事件队列后,仍然会拖慢整个事件循环的处理,甚至是阻塞。可见基于事件循环的异步模型仍然是有很大缺点的,这时候我们就需要Isolate,这个单词的中文意思是隔离。

简单说,可以把它理解为Dart中的线程。但它又不同于线程,更恰当的说应该是微线程,或者说是协程。它与线程最大的区别就是不能共享内存,因此也不存在锁竞争问题,两个Isolate完全是两条独立的执行线,且每个Isolate都有自己的事件循环,它们之间只能通过发送消息通信,所以它的资源开销低于线程。

从主Isolate创建一个新的Isolate有两种方法

spawnUri

static Future<Isolate> spawnUri()

spawnUri方法有三个必须的参数:

  • 第一个是Uri,指定一个新Isolate代码文件的路径,
  • 第二个是参数列表,类型是List<String>
  • 第三个是动态消息。

需要注意,用于运行新Isolate的代码文件中,必须包含一个main函数,它是新Isolate的入口方法,该main函数中的args参数列表,正对应spawnUri中的第二个参数。如不需要向新Isolate中传参数,该参数可传空List

Isolate中的代码:

  1. import 'dart:isolate';
  2. // 创建一个新的 isolate
  3. create_isolate() async {
  4. // 创建主Isolate的ReceivePort对象rp
  5. ReceivePort rp = new ReceivePort();
  6. // 创建rp对象的SendPort
  7. SendPort port1 = rp.sendPort;
  8. // 通过spawnUri方法,将port1传送到新Isolate的main函数中
  9. Isolate newIsolate = await Isolate.spawnUri(
  10. new Uri(path: "./other_task.dart"), //第一个参数:指定一个新Isolate代码文件的路径,
  11. ["hello, isolate", "this is args"], //第二个参数:参数列表,类型是List<String>
  12. port1); //第三个参数:动态消息
  13. // 创建一个空对象,用于接收rp2对象的SendPort
  14. SendPort port2;
  15. // 主Isolate创建监听,接收消息
  16. rp.listen((message) {
  17. print('主Isolate接收到消息: ${message}');
  18. if (message[0] == 0) {
  19. port2 = message[1]; //把rp2对象的SendPort传递过来
  20. } else {
  21. // 向新Isolate发送消息
  22. port2?.send([1, "这条信息是 主Isolate 发送的"]);
  23. }
  24. });
  25. // 可以在适当的时候,调用以下方法杀死创建的 isolate
  26. // newIsolate.kill(priority: Isolate.immediate);
  27. }
  28. void main() {
  29. print('主Isolate开始');
  30. create_isolate();
  31. print('主Isolate结束');
  32. }

创建other_task.dart文件,编写新Isolate的代码

  1. import 'dart:isolate';
  2. import 'dart:io';
  3. void main(args, SendPort port1) {
  4. print('新Isolate开始');
  5. print('新Isolate main函数的第一个参数: ${args}');
  6. // 创建新Isolate的ReceivePort对象rp2
  7. ReceivePort rp2 = new ReceivePort();
  8. // 创建rp2对象的SendPort
  9. SendPort port2 = rp2.sendPort;
  10. // 新Isolate建立监听,接收消息
  11. rp2.listen((message) {
  12. print('新Isolate接收到消息: ${message}');
  13. });
  14. // 向主Isolate发送消息,将rp2对象的SendPort发送到主isolate中,用于通信
  15. port1.send([0, port2]);
  16. // 模拟耗时5秒
  17. sleep(Duration(seconds: 5));
  18. // 向主Isolate发送消息
  19. port1.send([1, '我是新Isolate发送的消息']);
  20. print("新Isolate结束");
  21. }

运行主Isolate的结果:

  1. Isolate开始
  2. Isolate结束
  3. Isolate开始
  4. Isolate main函数的第一个参数: [hello, isolate, this is args]
  5. Isolate接收到消息: [0, SendPort]
  6. # 5秒后输出:
  7. Isolate结束
  8. Isolate接收到消息: [1, 我是新Isolate发送的消息]
  9. Isolate接收到消息: [1, 这条信息是 Isolate 发送的]

Future - 图3

整个消息通信过程如上图所示,两个Isolate是通过两对Port对象通信,一对Port分别由用于接收消息的ReceivePort对象,和用于发送消息的SendPort对象构成。其中SendPort对象不用单独创建,它已经包含在ReceivePort对象之中。需要注意,一对Port对象只能单向发消息,这就如同一根自来水管,ReceivePortSendPort分别位于水管的两头,水流只能从SendPort这头流向ReceivePort这头。因此,两个Isolate之间的消息通信肯定是需要两根这样的水管的,这就需要两对Port对象。

理解了Isolate消息通信的原理,那么在Dart代码中,具体是如何操作的呢?

ReceivePort对象通过调用listen方法,传入一个函数可用来监听并处理发送来的消息。
SendPort对象则调用send()方法来发送消息。
send方法传入的参数可以是null,num, bool, double,String, List ,Map或者是自定义的类。

在上例中,我们发送的是包含两个元素的List对象,第一个元素是整型,表示消息类型,第二个元素则表示消息内容。

Future - 图4

spawn

static Future<Isolate> spawn()

除了使用spawnUri,更常用的是使用spawn方法来创建新的Isolate,我们通常希望将新创建的Isolate代码和main Isolate代码写在同一个文件,且不希望出现两个main函数,而是将指定的耗时函数运行在新的Isolate,这样做有利于代码的组织和代码的复用。spawn方法有两个必须的参数,第一个是需要运行在新Isolate的耗时函数,第二个是动态消息,该参数通常用于传送主IsolateSendPort对象。

spawn的用法与spawnUri相似,且更为简洁,将上面例子稍作修改如下

  1. import 'dart:isolate';
  2. import 'dart:io';
  3. // 创建一个主Isolate
  4. create_isolate() async {
  5. ReceivePort rp = new ReceivePort();
  6. SendPort port1 = rp.sendPort;
  7. SendPort port2;
  8. // 创建新Isolate
  9. Isolate newIsolate = await Isolate.spawn(doWork, port1);
  10. rp.listen((message) {
  11. print('主Isolate接收到消息: ${message}');
  12. if (message[0] == 0) {
  13. port2 = message[1]; //把rp2对象的SendPort传递过来
  14. } else {
  15. // 向新Isolate发送消息
  16. port2?.send([1, '这条信息是 主Isolate 发送的']);
  17. }
  18. });
  19. }
  20. // 处理耗时任务
  21. doWork(port1) {
  22. print('新Isolate开始');
  23. ReceivePort rp2 = new ReceivePort();
  24. SendPort port2 = rp2.sendPort;
  25. rp2.listen((message) {
  26. print('新Isolate接收到消息: ${message}');
  27. });
  28. port1.send([0, port2]);
  29. sleep(Duration(seconds: 5)); //模拟耗时5秒
  30. port1.send([1, "doWork 任务完成"]);
  31. print('新Isolate结束');
  32. }
  33. void main() {
  34. print('主Isolate开始');
  35. create_isolate();
  36. print('主Isolate结束');
  37. }

运行结果:

  1. Isolate开始
  2. Isolate结束
  3. Isolate开始
  4. Isolate接收到消息: [0, SendPort]
  5. # 5秒后输出:
  6. Isolate结束
  7. Isolate接收到消息: [1, doWork 任务完成]
  8. Isolate接收到消息: [1, 这条信息是 Isolate 发送的]

无论是上面的spawn还是spawnUri,运行后都会包含两个Isolate,一个是主Isolate,一个是新Isolate,两个都双向绑定了消息通信的通道,即使新的Isolate中的任务完成了,它也不会立刻退出,因此,当使用完自己创建的Isolate后,最好调用newIsolate.kill(priority: Isolate.immediate);Isolate立即杀死。

Flutter 中创建Isolate

无论如何,在Dart中创建一个Isolate都显得有些繁琐,可惜的是Dart官方并未提供更高级封装。但是,如果想在Flutter中创建Isolate,则有更简便的API,这是由Flutter官方进一步封装ReceivePort而提供的更简洁API。
详细API文档

使用compute函数来创建新的Isolate并执行耗时任务

  1. import 'package:flutter/foundation.dart';
  2. import 'dart:io';
  3. // 创建一个新的Isolate,在其中运行任务doWork
  4. create_new_task() async{
  5. var str = "New Task";
  6. var result = await compute(doWork, str);
  7. print(result);
  8. }
  9. String doWork(String value){
  10. print("new isolate doWork start");
  11. // 模拟耗时5秒
  12. sleep(Duration(seconds:5));
  13. print("new isolate doWork end");
  14. return "complete:$value";
  15. }

compute函数有两个必须的参数,第一个是待执行的函数,这个函数必须是一个顶级函数,不能是类的实例方法,可以是类的静态方法,第二个参数为动态的消息类型,可以是被运行函数的参数。需要注意,使用compute应导入'package:flutter/foundation.dart'包。

使用场景

Isolate虽好,但也有合适的使用场景,不建议滥用Isolate,应尽可能多的使用Dart中的事件循环机制去处理异步任务,这样才能更好的发挥Dart语言的优势。

那么应该在什么时候使用Future,什么时候使用Isolate呢?
一个最简单的判断方法是根据某些任务的平均时间来选择:

  • 方法执行在几毫秒或十几毫秒左右的,应使用Future
  • 如果一个任务需要几百毫秒或之上的,则建议创建单独的Isolate

除此之外,还有一些可以参考的场景

  • JSON 解码
  • 加密
  • 图像处理:比如剪裁
  • 网络请求:加载资源、图片

参考资料:
Dart 文档
Isolate 文档