Flutter 的线程模型和异步原理

  • Post author:
  • Post category:其他


d29dd39c076fe6473a21b2e02fe4df61.gif

本文字数::

36130

预计阅读时间:

91

分钟



Android

应用中, 用户时常会遇到界面卡顿的情况,非常影响用户的体验。作为

Android

开发肯定都知道:应用在主线程里做了大量的耗时操作(例如文件读写, 数据库读写,网络访问,图片编解码等),就会导致

UI

不能及时刷新,出现跳帧现象。如果应用主线程不能及时处理用户的输入事件或广播消息,系统甚至会直接弹出著名的

ANR

对话框,提示用户杀死应用。



Flutter

应用中,如果出现界面卡顿,它的原因也是如此吗?

我们带着这些疑问,一起来搞清楚

Flutter

的线程模型和异步原理,并找到问题的答案。

一、Flutte

r系统结


首先,我们熟悉下

Flutter

官方提供的系统结构图:

df4d2548f132c1f47158d4c1a0d86cc7.jpeg

整体框架是采用分层设计的,自上而下分别是:

Framework



Engine



Embedder


  • Framework

    :基于

    Dart

    语言构建的

    Framework

    ,包括了各种

    UI

    组件,动画和手势识别等,并将所有的设计通过

    Widgets

    小组件层进行抽象封装。所以在

    Flutter

    中一切都是

    Widget


  • Engine

    :基于

    C/C++

    构建的引擎,包括了

    Skia



    Dart



    Text

    等,实现了

    Flutter

    渲染引擎,文字排版,事件处理和

    Dart

    运行时等功能。

    Skia



    Text

    为上层提供调用底层渲染和排版的功能,

    Dart

    运行时提供了调用

    Dart

    和渲染引擎的能力;


  • Embedder

    :嵌入层是操作系统适配层,会将

    Flutter

    嵌入到各个平台上。嵌入层负责适配原生平台插件、线程管理、渲染

    Surface

    设置等。

从架构图中可以看到,

Embedder

负责线程的创建和管理,并且提供

Task Runner



Engine

使用。

Engine

虽然自己并不创建和管理线程,但是它通过

Dart VM(Dart Runtime Mgmt)

提供

Isolate



Framework

和应用层进行多线程创建。


二、Task Runner

接下来,我们继续了解

Embeder

提供的四个

Task Runner



Platform Runner



UI Runner



GPU Runner



IO Runner


Flutter

的代码基本上由这四个

Runner

负责运行,每个

Runner

负责不同的任务, 不只处理

Engine

的任务,还处理

Native Plugin

带来的原生平台任务。



Android

中,每个

Flutter Engine

各自拥有一个

UI Runner



GPU Runner



IO Runner

,但是一个应用中的所有

Engine

共享一个

Platform Runner

。每个

Runner

都是一个平台线程,且

Engine

会将

UI Runner



Root Isolate

进行互相绑定。但

Runner



Isolate

本身是相互独立的,

Isolate



Dart VM

进行管理,不由

Runner

管理。


  • Platform Runner


Platform Runner

运行在平台的

Main Thread

,负责执行

Flutter Engine

的代码和

Native Plugin

任务。

如果在

Platform Runner

中运行耗时任务,会影响原生平台任务的正常执行。但是

Platform Runner

被阻塞后并不会导致页面卡顿。因为

Platform Runner

不负责

Flutter

的页面渲染,这点和

Android

原生应用不一样。


  • UI Runne


    r


UI Runner

负责为

Engine

执行

Root Isolate

的代码,而

Root Isolate

负责运行所有

Dart

代码。


Root Isolate

绑定了很多

UI Runner

的处理函数,负责创建管理

Layer Tree

最终绘制到屏幕上的内容,因此这个线程被阻塞会直接导致界面卡顿掉帧。

每当页面更新的

vsync

到来时,

Root Isolate

会对

Widgets

进行

layout

,生成

Layer tree

等页面显示信息,提交给

Engine

去处理。

所以,在

Root Isolate

中运行耗时任务会导致页面显示卡顿。


  • GPU Runn


    er


GPU Runner

负责将

UI Runner

提供的

Layer Tree

信息转化为平台可执行的

GPU

指令,并提交给渲染平台,如

Skia


GPU Runner

还负责管理绘制所需要的

GPU

资源,比如平台

Framebuffer



Surface



Texture



Buffers

等。


GPU Runner

相对比较独立,除了

Embedder



Runner

线程外,其他线程均不可向其提交渲染信息。


  • IO Runner


IO Runner

负责将读取的图片解压转换成

GPU

能够处理的格式并提交给

GPU Runner

处理。



Image

这样的资源通过

async call

调用时,

Framework

会通知

IO Runner

进行图片的异步加载,进行格式处理,然后通过

GPU Runner



Context

引用,提交给

GPU Runner

处理。

由上可知,在

Android  Flutter

应用中,如果出现界面卡顿,它的原因和

Android

应用的原因并不相同。

Flutter

应用中平台线程的阻塞不会影响界面的卡顿,而

UI Runner

的阻塞必然导致页面卡顿。

那么,在

Flutter

应用中,像网络请求,文件读取,海量计算,图片处理,编解码等耗时任务都应该怎么处理,才能不阻塞

UI Runner

呢?

要解释清楚这个问题,我们需要先了解

Flutter

中的线程模型:

Isolate



三、线程模型


Isolate



Dart

平台对线程的实现方案,所以和线程一样,也可以利用多核

CPU

去处理大量耗时任务。


Isolate

底层实际还是使用操作系统提供的

OSThread

。但和普通

Thread

不同,

Isolate

拥有独立的内存,由线程加独立内存构成。

由于

Isolate

线程之间内存不共享,所以

Isolate

线程之间并不存在共享数据的问题,所以也不需要

Isolate

数据同步机制。


Isolate

之间虽然不能共享数据,但是可以通过端口 Port 的方式进行数据通信。

3.1 Isolate



Android Flutter

应用启动后,会首先执行

main

函数,接着调用

runApp

,然后创建一个

Flutter Engin



Engin

会启动四个

Task Runner

,并且

UI Runner

开始执行

Root Isolate

主线程中的代码。


Flutter

应用默认在单线程中运行

Dart

代码,如果不开启新的线程,所有

Dart

代码都在

Root Isolate

主线程中运行。

我们可以通过

Isolate

的创建和

Port

数据通信的例子来进一步了解代码执行的线程情况:

// kiki_main_tab_page.dart
class KKMainTabPageState extends BaseThemeState<KKMainTabPage>with WidgetsBindingObserver {
  // 这是摸鱼kik的主界面
  ......

  @override
  void initState() {
    super.initState();
    ......
 
    //测试 Future 中代码的运行所属 Isolate
    Future.delayed(const Duration(milliseconds: 500))
    .then((value) {
        KKMethodChannelUtil.setAndroidStatusBarTheme(AppScreenMedia.isLight);
        print_cjf('initState 1---' + Isolate.current.debugName.toString());
       });

    print_cjf('initState 2---' + Isolate.current.debugName.toString());
    //测试在新的 isolate 中请求数据。此处没有用await等待方法的异步结果。
    testIsolate(); 
    print_cjf('initState 3---' + Isolate.current.debugName.toString());
  }
    
  @override
  Widget build(BuildContext context) {
    print_cjf('build 4---' + Isolate.current.debugName.toString());
    ......
  }
  ......
}

// test_isolate.dart
void testIsolate() async {
  print_cjf('testIsolate start---' + Isolate.current.debugName.toString());
   
  //创建 ReceivePort,用来接受新 Isolate 发送的消息
  ReceivePort receivePort1 = ReceivePort();
  print_cjf('testIsolate 1---' + Isolate.current.debugName.toString());
   
  //创建新的 Isolate,并且把 receivePort1 的发送端口传给 newIsolate
  var newIsolate = await Isolate.spawn(dataLoader, receivePort1.sendPort);
  print_cjf('testIsolate 2---' + Isolate.current.debugName.toString());
   
  //等待 newIsolate 发送的异步消息(异步消息只有 newIsolate 的发送端口 receivePort3.sendPort)。
  //通过 first 获得异步消息后,会立即关闭 receivePort1 的 sendPort 端口。
  SendPort sendPort3 = await receivePort1.first;
  print_cjf('testIsolate 3---' + Isolate.current.debugName.toString());
   
  //因为上面 first 函数获取异步消息后,关闭了 receivePort1 的 sendPort 端口
  //所以要新创建一个新的 ReceivePort 接受 newIsolate 发送过来的消息
  ReceivePort receivePort2 = ReceivePort();
  print_cjf('testIsolate 4---' + Isolate.current.debugName.toString());
   
  //使用 newIsolate 的发送端口 sendPort3,发送异步消息(异步消息包括网络 url,和 rootIsolate 的 receivePort2 发送端口)
  sendPort3.send(['https://jsonplaceholder.typicode.com/posts/1',receivePort2.sendPort]);
  print_cjf('testIsolate 5.1---' + Isolate.current.debugName.toString());
  sendPort3.send(["https://w.sohu.com/detail/1",receivePort2.sendPort]);
  print_cjf('testIsolate 5.2---' + Isolate.current.debugName.toString());
  sendPort3.send(["aaaaaa",receivePort2.sendPort]);
  print_cjf('testIsolate 5.3---' + Isolate.current.debugName.toString());
  sendPort3.send(["bbbbbb",receivePort2.sendPort]);
  print_cjf('testIsolate 5.4---' + Isolate.current.debugName.toString());
   
  //获取 newIsolate 发送来的异步消息(异步获取的网络数据)
  //方法一: 不等待 循环获取异步数据
  receivePort2.listen((msg) {
    print_cjf('testIsolate 6.2---' + Isolate.current.debugName.toString() + '---$msg');
  });

  print_cjf('testIsolate end---' + Isolate.current.debugName.toString());
}
 
void dataLoader(SendPort sendPort1) async {
  print_cjf('dataLoader start---' + Isolate.current.debugName.toString());
   
  ReceivePort receivePort3 = ReceivePort();
  sendPort1.send(receivePort3.sendPort);
   
  print_cjf('dataLoader 1---' + Isolate.current.debugName.toString());
   
  await for (var msg in receivePort3) {
    String dataUrl = msg[0];
    SendPort sendPort2 = msg[1];
     
    // 暂不从网络中获取数据,直接模拟一个数据返回
    sendPort2.send('dataLoader 2.1---' + Isolate.current.debugName.toString() + "---$dataUrl");
    print_cjf('dataLoader 3.1---' + Isolate.current.debugName.toString() + "---$dataUrl");
    
    if(dataUrl.startsWith("aaaaaa")) {
      //销毁当前 isolate, 并发送结束消息给 sendPort2
      Isolate.exit(sendPort2, "dataLoader 4 ---Isolate exit with last message to sendPort2");
    }
  }
 
  print_cjf('dataLoader end---' + Isolate.current.debugName.toString()); //不会被执行到。
}

// 输出 log 
I/flutter ( 4910): 11:25:56:587:cjf---initState 2---main //KKMainTabPageState 的 initState, 开始调用
I/flutter ( 4910): 11:25:56:588:cjf---testIsolate start---main 
I/flutter ( 4910): 11:25:56:589:cjf---testIsolate 1---main //调用 await Isolate.spawn 创建新 isolate,testIsolate 方法中断执行,需等待异步结果回来后恢复执行。
I/flutter ( 4910): 11:25:56:590:cjf---initState 3---main //KKMainTabPageState 的 initState,await Isolate.spawn 会进行异步调度,所以会返回到 initState 中继续执行
I/flutter ( 4910): 11:25:56:591:cjf---build 4---main //KKMainTabPageState 的 build,后续还会被多次调用

I/flutter ( 4910): 11:25:56:592:cjf---testIsolate 2---main //异步执行,await Isolate.spawn 的异步结果返回,testIsolate 后续代码继续执行
I/flutter ( 4910): 11:25:57:086:cjf---initState 1---main //异步执行,Future.delayed 时间到了后,异步任务会得到执行,且运行在 Root Isolate 中

I/flutter ( 4910): 11:25:57:133:cjf---dataLoader start---dataLoader//新建 isolate 内部, 新的 isolate 开始运行
I/flutter ( 4910): 11:25:57:134:cjf---dataLoader 1---dataLoader //新建 isolate 内部, 将自己的 SendPort3 发送给 RootIsolate 的 SendPort1

I/flutter ( 4910): 11:25:57:140:cjf---testIsolate 3---main //异步执行,await receivePort1.first,仍然在 root isolate 中
I/flutter ( 4910): 11:25:57:141:cjf---testIsolate 4---main

I/flutter ( 4910): 11:25:57:142:cjf---testIsolate 5.1---main //给 SendPort3 发送 jsonplaceholder.typicode.com 数据和 sendPort2
I/flutter ( 4910): 11:25:57:144:cjf---testIsolate 5.2---main //给 SendPort3 发送 w.sohu.com 数据和 sendPort2
I/flutter ( 4910): 11:25:57:144:cjf---dataLoader 3.1---dataLoader---https://jsonplaceholder.typicode.com/posts/1
I/flutter ( 4910): 11:25:57:144:cjf---testIsolate 5.3---main //给 SendPort3 发送 aaaaaa 数据和 sendPort2
I/flutter ( 4910): 11:25:57:145:cjf---dataLoader 3.1---dataLoader---https://w.sohu.com/detail/1
I/flutter ( 4910): 11:25:57:145:cjf---testIsolate 5.4---main //给 SendPort3 发送 bbbbbb 数据和 sendPort2
I/flutter ( 4910): 11:25:57:146:cjf---testIsolate end---main //设置 listen 数据,退出 testIsolate 方法
I/flutter ( 4910): 11:25:57:146:cjf---dataLoader 3.1---dataLoader---aaaaaa

I/flutter ( 4910): 11:25:57:148:cjf---testIsolate 6.2---main---dataLoader 2.1---dataLoader---https://jsonplaceholder.typicode.com/posts/1
I/flutter ( 4910): 11:25:57:149:cjf---testIsolate 6.2---main---dataLoader 2.1---dataLoader---https://w.sohu.com/detail/1
I/flutter ( 4910): 11:25:57:149:cjf---testIsolate 6.2---main---dataLoader 2.1---dataLoader---aaaaaa
I/flutter ( 4910): 11:25:57:150:cjf---testIsolate 6.2---main---dataLoader 4 ---Isolate exit with last message to sendPort2

I/flutter ( 4910): 11:25:57:588:cjf---build 4---main //KKMainTabPageState 的 build,被多次调用

可以从上述例子,

Log

和注释可以看到:


  • Root Isolate



    debugName

    也是

    "main"

    ,而不是

    "root"

  • 通过

    Isolate.spawn

    创建新的

    Isolate

    ,

    debugName

    即为方法名

    "dataLoader"

  • 除了新建

    Isolate

    中的代码,其它

    Dart

    代码都默认运行在

    Root Isolate

    中,包括

    widget

    中的代码,

    Future

    中的代码,和

    await

    异步方法中的代码。如:

    "initState 3---main"



    "build 4---main"

    ,

    "initState 1---main"



    "testIsolate 1---main"

    都在

    "main"

    线程中运行;


  • await

    方法调用,需要等待异步任务结果,会停止

    当前方法

    后续代码的执行,但不会阻塞

    调用者

    后续代码的执行,如

    log "initState 3"

    的输出并没有等待

    log "testIsolate 2"

    的输出。包括

    widget

    的构建

    log "build 4"

    , 和

    initState

    中的

    Future delay

    异步任务的

    log "initState 1"

    都得到了执行,而不是一直阻塞等待

    testIsolate

    方法中的

    await Isolate.spawn



    await receivePort1.first

    语句的返回;


  • Isolate

    之间通过

    ReceivePort

    进行信息发送和接送,且消息传递的过程是异步的。如,

    log "testIsolate 5.1"



    "testIsolate 5.2"



    "testIsolate 5.3"



    "testIsolate 5.4"



    log "dataLoader 3.1"

    是穿插着打印出来的;


  • Isolate

    之间可以通过

    await receivePort1.first

    来阻塞式接收一次消息,或通过

    receivePort2.listen

    来非阻塞式循环接收信息,或通过

    await for receivePort3

    来阻塞式循环接收信息。

从例子中我们不但可以看到

Isolate

的创建和

Port

数据通信方式,同时也看到了

Dart

代码都默认运行在

Root Isolate

主线程中。

3.2 compute

上面例子中创建一个

Isolate

进行数据通讯,步骤较为麻烦。为此

Flutter

为我们提供了一个简版的

Isolate



compute


compute

很适合简单,高

CPU

的耗时任务处理,它可以很便捷地进行多线程任务开发,我们举例进行说明:

// test_isolate.dart
void testCompute() async {
  print_cjf('testCompute start---' + Isolate.current.debugName.toString());

  Future.delayed(new Duration(seconds: 1),(){
    print_cjf('testCompute 1---' + Isolate.current.debugName.toString());
  });

  var count = await compute(countTask, 1234567890);

  print_cjf('testCompute end---' + Isolate.current.debugName.toString()+ "---count=" + count.toString());
}

int countTask(int num1) {
  print_cjf('countTask start ---' + Isolate.current.debugName.toString());

  int count = 0;
  while (num1 > 0) {
    if (num1 % 2 == 0) { count++; }
    num1--;
  }

  print_cjf('countTask end---' + Isolate.current.debugName.toString());

  return count;
}

// 输出 Log
I/flutter ( 4910): 12:44:32:731:cjf---testCompute start---main
I/flutter ( 4910): 12:44:33:254:cjf---countTask start ---Closure: (int) => int from Function 'countTask': static.
I/flutter ( 4910): 12:44:33:736:cjf---testCompute 1---main
I/flutter ( 4910): 12:45:16:329:cjf---countTask end---Closure: (int) => int from Function 'countTask': static.
I/flutter ( 4910): 12:45:16:330:cjf---testCompute end---main---count=617283945


compute

方法是

Flutter foundation

包中的顶层方法,它对

Isolate.spawn



sendPort

消息发送做了封装,返回一个异步结

Future<R>

通过封装后的接口,我们只需简单调用

await compute

,就可以等待一个耗时 43s 的计算任务的异步执行结果,并且等待过程中没有阻塞

Root Isolate

中其它异步任务地执行,比如,通过

Future.delayed

插入的异步任务

"testcompute 1---main"

,在

countTask Isolate

执行的同时,在

Root Isolate

中被执行了。

3.3 Memory

上面 2 个例子,要么是直接等待新

Isolate

的异步结果,要么是通过

port

进行数据传输,并没有使用全局数据进行共享,所有也没有数据同步问题。

实际上在

Flutter

中确实没有类似

Android

的全局共享数据,因为

Flutter

中的

Isolate

拥有独立的内存,数据没法共享,只能通过

port

传输。

我们继续通过一个简单的例子,来说明

Isolate

独立内存这个特点:

// test_isolate.dart
int intValue = 0; //定义普通 int 顶层变量
IntObject intObject = IntObject(); //定义对象 IntObject 顶层变量

class IntObject {
  int _i = 0;
  void increase() { _i++; }
  int get() { return _i;}
}

void testIsolateMemory() async {
  print_cjf('testIsolateMemory start');
  final receive = ReceivePort();

  receive.listen((msg) {
    //打印 MemoryTask Isolate 传过来的String
    print_cjf('testIsolateMemory ---'+ "data===$msg");
    //打印 Root Isolate 中的变量值
    print_cjf('testIsolateMemory ---'+ "i=$intValue, intObject=${intObject.get()}");
  });

  //5s后,给顶层变量加1
  Future.delayed(const Duration(seconds: 5),(){
    intValue++;
    intObject.increase();
    print_cjf('testIsolateMemory ---'+ "delayed:i=$intValue, intObject=${intObject.get()}");
  });

  Isolate isolate = await Isolate.spawn(MemoryTask, receive.sendPort);

  print_cjf('testIsolateMemory end');
}

void MemoryTask(SendPort sendPort) {
  int counter = 0; //MemoryTask Isolate 中的局部变量
  print_cjf('MemoryTask start');

  //每隔 1s,给顶层变量和局部变量都加1
  Timer.periodic(const Duration(seconds: 1), (_) {
    counter++;
    intValue++;
    intObject.increase();

    String sendMsg = "counter=$counter, i=$intValue, intObject=${intObject.get()}";
    //打印 MemoryTask Isolate 中的变量值
    print_cjf('MemoryTask ---$sendMsg');
    sendPort.send(sendMsg);

    if(counter >= 10) {
      //销毁当前 isolate, 并发送结束消息给 sendPort
      Isolate.exit(sendPort, "MemoryTask ---Isolate exit with last message to sendPort");
    }
  });

  print_cjf('MemoryTask end');
}

// 输出 Log
I/flutter ( 4910): 12:58:21:555:cjf---testIsolateMemory start
I/flutter ( 4910): 12:58:21:575:cjf---testIsolateMemory end
I/flutter ( 4910): 12:58:22:84:cjf---MemoryTask start
I/flutter ( 4910): 12:58:22:86:cjf---MemoryTask end

I/flutter ( 4910): 12:58:23:93:cjf---MemoryTask ---counter=1, i=1, intObject=1//MemoryTask Isolate 中的顶层
变量发生改变
I/flutter ( 4910): 12:58:23:96:cjf---testIsolateMemory ---data===counter=1, i=1, intObject=1
I/flutter ( 4910): 12:58:23:97:cjf---testIsolateMemory ---i=0, intObject=0 //Root Isolate 中的顶变量没有改变
I/flutter ( 4910): 12:58:24:87:cjf---MemoryTask ---counter=2, i=2, intObject=2
I/flutter ( 4910): 12:58:24:89:cjf---testIsolateMemory ---data===counter=2, i=2, intObject=2
I/flutter ( 4910): 12:58:24:90:cjf---testIsolateMemory ---i=0, intObject=0 //Root Isolate 中的顶变量没有改变
I/flutter ( 4910): 12:58:25:89:cjf---MemoryTask ---counter=3, i=3, intObject=3
I/flutter ( 4910): 12:58:25:90:cjf---testIsolateMemory ---data===counter=3, i=3, intObject=3
I/flutter ( 4910): 12:58:25:91:cjf---testIsolateMemory ---i=0, intObject=0 //Root Isolate 中的顶变量没有改变
I/flutter ( 4910): 12:58:26:89:cjf---MemoryTask ---counter=4, i=4, intObject=4
I/flutter ( 4910): 12:58:26:90:cjf---testIsolateMemory ---data===counter=4, i=4, intObject=4
I/flutter ( 4910): 12:58:26:91:cjf---testIsolateMemory ---i=0, intObject=0 //Root Isolate 中的顶变量没有改变

I/flutter ( 4910): 12:58:26:621:cjf---testIsolateMemory ---delayed:i=1, intObject=1 //Root Isolate 中给顶层变量加1

I/flutter ( 4910): 12:58:27:88:cjf---MemoryTask ---counter=5, i=5, intObject=5
I/flutter ( 4910): 12:58:27:90:cjf---testIsolateMemory ---data===counter=5, i=5, intObject=5
I/flutter ( 4910): 12:58:27:90:cjf---testIsolateMemory ---i=1, intObject=1 //Root Isolate 中的顶层变量发生改变

I/flutter ( 4910): 12:58:28:89:cjf---MemoryTask ---counter=6, i=6, intObject=6
I/flutter ( 4910): 12:58:28:90:cjf---testIsolateMemory ---data===counter=6, i=6, intObject=6
I/flutter ( 4910): 12:58:28:91:cjf---testIsolateMemory ---i=1, intObject=1
I/flutter ( 4910): 12:58:29:87:cjf---MemoryTask ---counter=7, i=7, intObject=7
I/flutter ( 4910): 12:58:29:89:cjf---testIsolateMemory ---data===counter=7, i=7, intObject=7
I/flutter ( 4910): 12:58:29:90:cjf---testIsolateMemory ---i=1, intObject=1
I/flutter ( 4910): 12:58:30:89:cjf---MemoryTask ---counter=8, i=8, intObject=8
I/flutter ( 4910): 12:58:30:91:cjf---testIsolateMemory ---data===counter=8, i=8, intObject=8
I/flutter ( 4910): 12:58:30:92:cjf---testIsolateMemory ---i=1, intObject=1
I/flutter ( 4910): 12:58:31:89:cjf---MemoryTask ---counter=9, i=9, intObject=9
I/flutter ( 4910): 12:58:31:91:cjf---testIsolateMemory ---data===counter=9, i=9, intObject=9
I/flutter ( 4910): 12:58:31:91:cjf---testIsolateMemory ---i=1, intObject=1
I/flutter ( 4910): 12:58:32:89:cjf---MemoryTask ---counter=10, i=10, intObject=10
I/flutter ( 4910): 12:58:32:90:cjf---testIsolateMemory ---data===counter=10, i=10, intObject=10
I/flutter ( 4910): 12:58:32:91:cjf---testIsolateMemory ---i=1, intObject=1

I/flutter ( 4910): 12:58:32:91:cjf---testIsolateMemory ---data===MemoryTask ---Isolate exit with last message to sendPort
I/flutter ( 4910): 12:58:32:92:cjf---testIsolateMemory ---i=1, intObject=1

我们从输出

log

可以看出,


  • MemoryTask Isolate

    中,每隔 1s 就给顶层变量加 1,但是,

    Root Isolate

    中的变量并不会同步改变;


  • Root Isolate

    中 5s 后给顶层变量加 1,也只影响了

    Root Isolate

    中读取的顶层变量值,不会影响到

    MemoryTask Isolate

    中读取的顶层变量值。

由此可见,跨

Isolate

数据共享只能通过

port

的方式。

Isolate

中的内存是独立的,它们不存在数据共享,当然也就不需要处理共享数据的线程同步问题。


Isolate

中内存独立的特点,和

Java

中的线程局部存储

ThreadLocal

有点类似。

Android



Looper

类里的静态变量

sThreadLocal

,就属于线程局部存储,不同线程中调用

Looper.prepare

设置到

sThreadLocal

中的

Looper

对象各不相同,取出来使用的对象也不相同。我们可以参考对比其源码,如下:

//Looper.java
public final class Looper {
  static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
  ......
  private static void prepare(boolean quitAllowed) {
      if (sThreadLocal.get() != null) {
          throw new RuntimeException("Only one Looper may be created per thread");
      }
      sThreadLocal.set(new Looper(quitAllowed));
  }
  ......
}

通过

Dart

源码也可以看到,

Isolate

的创建过程,包括了创建

Isolate

结构体,在堆中分配线程内存,创建线程和使用线程池等代码,也进一步说明了

Isolate

是具有独立内存的线程。

了解完

Isolate

线程模型,我们就明白了:我们所写的

Dart

代码默认是运行在

Root Isolate

中,而

Root Isolate

是运行在

UI Runner

上的,所以如果我们写的

Dart

代码过于耗时,必然导致负责管理绘制的

UI Runner

不能及时刷新页面,导致页面卡顿。因此,在

Flutter

应用中也需要使用多线程去处理耗时任务。

但是已有的使用经验中,我们并没有单独创建

Isolate

去处理网络请求任务,这与上述结论不就矛盾了吗?

我们先继续学习

Flutter

的异步原理,再来解答这个问题。

四、异步原理


Flutter

中,如果不单独创建

Isolate

的话,可以说是单线程模型,它通过




线程异步

方案支持大量并发操作。

大家容易把异步,并行和并发这几个概念搞混。我们先来了解下并行和并发的概念:


  • 并行




    指的是多个

    CPU

    ,在同一时间里执行多个任务;


  • 并发




    指的是一个

    CPU

    ,轮换着去处理多个任务,由

    系统

    来管理任务的切换。


并行的实现

,必须由多线程来完成。多线程的方式可以利用多

CPU

的并行优势,同时执行多个任务。一般通过线程池来管理和复用大量线程。


并发的实现




多采用

单线程+非阻塞+事件通知

的方式来完成。因为线程切换的消耗是比较大的,不适合大量创建,所以并发的实现多采用

单线程

。阻塞式中断是在执行任务时将线程阻塞,等待执行完成后再恢复线程执行,无法达到异步效果。而非阻塞式中断是在执行任务时,保存当前上下文,不等待任务结果,继续调度当前线程的其它任务。调度当前线程的其它任务就依赖于事件通知。



异步

是相对于同步来说,它指程序的执行顺序与任务的排列顺序是不一致的。异步属于并发,不属于并行。

上面的描述,可能比较抽象,不易理解,我们继续通过图和示例来学习

Flutter

的异步原理。

4.1 事件循环和消息队列



Flutter

中,

Isolate

是通过事件循环和消息队列来实现异步的。每个

Isolate

包含一个事件循环以及两个事件队列:


  • Event Loop

    :事件循环,负责无限循环读取微任务队列和事件队列进行处理;


  • Microtask queue

    :微任务事件队列,优先级比

    Event queue

    高,应用可以向

    Isolate

    添加微任务;


  • Event queue

    :普通事件队列,包括

    IO

    事件,绘制事件,手势事件,及应用添加的外部事件。

事件循环和事件队列的执行流程如下图:

7f1b086ee4577d3eb70e593be39597e9.png



Root Isolate

中,

Event queue

包括了绘制事件和手势事件,如果它们不能得到及时处理,会导致渲染、手势响应延时,出现卡顿现象。

为了保证渲染和手势得到及时响应,我们应该尽量不要向

Microtask queue

中添加事件,因为它的处理优先级比

Event queue

要高。

即便向

Event queue

中添加事件时,也不能添加过于耗时的事件,避免影响后续的渲染和手势事件得不到及时响应,影响用户使用体验。

我们可以通过

Future



await



Event queue

中插入任务,也可以通过

scheduleMicrotask



Microtask queue

添加任务。

下面通过举例,进一步进行说明:

// test_isolate.dart
void testMicroTask() async{
  print_cjf('testMicroTask start');

  new Future(() => print_cjf('future 1-1')) //创建异步任务 future 1
      .then((_){
          new Future(()=>print_cjf('future 2')); //创建异步任务 future 2
          scheduleMicrotask(() =>print_cjf('microtask 3'));//创建异步任务 microtask 3。比同层的 future 2 先执行

          print_cjf('future 1-2');
      })
      .then( (_)=>print_cjf('future 1-3') );

  scheduleMicrotask(() =>print_cjf('microtask 4'));//创建异步任务 microtask 4。 比同级别的 future 1 先执行
  
  print_cjf('testMicroTask 1');

  await funDelay(); //创建异步任务 funDelay。调用耗时方法,使用 await 等待其结果返回

  print_cjf('testMicroTask end'); // 异步执行
}

Future<int> funDelay() async{ //声明异步方法, 必须使用 async
  print_cjf('funDelay start'); // 同步执行

  await Future.delayed(Duration(seconds: 1)); //见 log1,调用 1s 耗时方法,使用 await 等待
  // await Future.delayed(Duration(milliseconds: 1)); //见 log2,调用 1ms 耗时方法,使用 await 等待

  print_cjf('funDelay end'); // 异步执行
  return 2;
}

//输出 log1
I/flutter ( 4910): 17:51:25:216:cjf---testMicroTask start
I/flutter ( 4910): 17:51:25:216:cjf---testMicroTask 1 //同步执行
I/flutter ( 4910): 17:51:25:216:cjf---funDelay start //同步执行

I/flutter ( 4910): 17:51:25:217:cjf---microtask 4 //第一层 microtask
I/flutter ( 4910): 17:51:25:217:cjf---future 1-1 //第一层 future
I/flutter ( 4910): 17:51:25:218:cjf---future 1-2 //第一层 future 的链式调用
I/flutter ( 4910): 17:51:25:218:cjf---future 1-3 //第一层 future 的链式调用

I/flutter ( 4910): 17:51:25:218:cjf---microtask 3 //第二层 microtask
I/flutter ( 4910): 17:51:25:218:cjf---future 2 //第二层 future

I/flutter ( 4910): 17:51:26:218:cjf---funDelay end //属于第一层 future,但由于时间条件不满足,比第二层future更后执行了
I/flutter ( 4910): 17:51:26:219:cjf---testMicroTask end //属于第一层 future

//输出 log2
I/flutter ( 9669): 17:58:34:948:cjf---testMicroTask start
I/flutter ( 4910): 17:58:34:950:cjf---testMicroTask 1 //同步执行
I/flutter ( 9669): 17:58:34:950:cjf---funDelay start //同步执行

I/flutter ( 9669): 17:58:34:951:cjf---microtask 4//第一层 microtask
I/flutter ( 9669): 17:58:35:94:cjf---future 1-1//第一层 future
I/flutter ( 9669): 17:58:35:95:cjf---future 1-2//第一层 future 的链式调用
I/flutter ( 9669): 17:58:35:96:cjf---future 1-3//第一层 future 的链式调用

I/flutter ( 9669): 17:58:35:96:cjf---microtask 3//第二层 microtask

I/flutter ( 9669): 17:58:35:97:cjf---funDelay end //属于第一层 future
I/flutter ( 9669): 17:58:35:97:cjf---testMicroTask end //属于第一层 future

I/flutter ( 9669): 17:58:35:97:cjf---future 2//第二层 future

上述例子中,在

funDelay

等待 1s 和 1ms 的情况,分别输出了

log1



log2

。通过对比输出的

log

, 我们可以清晰的看到:


  • Microtask



    Future

    添加的任务都会被异步执行;

  • 同层的

    Microtask

    会比

    Future

    优先被处理。因为

    new Future

    创建的异步事件被添加到了

    Event queue

    ,而

    scheduleMicrotask

    创建的异步事件被添加到了

    MicroTask queue

    中,而事件循环总是会优先处理

    MicroTask queue

    中的事件;


  • await funDelay

    后的代码

    "funDelay end"

    ,属于第一层

    future


  • await

    的运行受返回结果的时机影响:‍



  • log1

    中,

    await funDelay

    需要 1s 时,事件循环处理到它时,发现时间条件不满足后跳过,所以

    "funDelay end"

    会比第二层的

    "future 2"

    后执行;



  • log2

    中,如果

    await funDelay

    只需要 1ms 时,

    "funDelay end"

    就会比第二层的

    "future 2"

    先执行;

  • 但无论是

    log1

    还是

    log2



    "funDelay end"

    都会比第二层的

    "microtask3"

    后执行,因为

    microtask

    总是会被优先执行,即使更后被添加到

    microtask

    队列。

在上述例子中,

await funDelay

调用也会将后续代码处理事件,如

"testMicroTask end"

,添加到

Event queue

,属于第一层事件,会比第二层事件

"future 2"



Event queue

中的位置更靠前,如果条件符合的话,会比第二层的

"future 2"

事件更先被执行。


"microtask 3"

虽然是第二层

MicroTask

,但是在第一层

"future 1"

执行时被添加到

Microtask queue

中。当

"future 1 "

执行完,回到

Event loop

循环时,会优先处理

Microtask queue

中的事件,所以第二层的

"microtask 3"

会比 第一层的

"funDelay end"

先执行。

下面用图来更形象地描述上述例子,我们可以更清晰的看到事件队列的添加和消费情况:

203752134385fbfcedcfe76f2c51648f.png

通过上面的示例,我们可以看到在

Flutter

中进行异步调用,一般需要用到三个关键词:

Future



async



await

。其中

async



await

需要一起使用。


  • Future



    Future

    表示异步操作的返回结果,可以通过

    then

    处理返回结果。和

    Java

    中的

    Future

    功能差别巨大,注意区别使用;


  • async

    :标记某个方法为异步方法,在声明方法的时候使用。其返回值是

    Future

    类型;


  • await

    :表示等待某个异步方法的结果,一般调用耗时异步方法时使用。

下面我们继续深入

Future



async



await

的使用和原理学习。

4.2 Future



Dart

中可以通过

Future

进行异步操作,

Future

异步的特点是提供了链式调用,可以解决回调地狱,异常捕获,和代码执行依赖等问题。


Future<T>

表示一个指定类型的异步操作结果,如果不需要结果可以使用

Future<void>

。我们可以直接使用

Future

创建一个异步操作结果,也可以通过调用

async

方法得到一个异步操作结果。

  • 当创建一个异步事件时,会直接返回一个

    Future

    ,后续代码可以继续执行,不会被阻塞;



  • Future

    中的结果返回时,如果注册了

    then

    结果回调,

    onValue

    回调会拿到成功的返回值;



  • Future

    中出现执行异常时,如果注册了

    catchError

    失败回调,

    onError

    回调会拿到失败的异常信息和错误栈 。

我们通过一个简单的例子,进一步了解

Future

// test_isolate.dart
void testFuture() async{
  print_cjf('testFuture start');

  Future f1 = new Future(() {
        print_cjf('future 1-1');
        return "aaa";})
      .then((value) {
        print_cjf('future 1-2' + "---$value");
        // throw 'bbb Error!'; //见 log2-1,log2-2
        return "bbb"; //见 log1
      })
      .then((value) => print_cjf('future 1-3' + "---$value") )
      .catchError((err, stackTrace) {
          print_cjf('Caught $err' +"---$stackTrace");
          return "ccc";
        }, test: (err) {
          print_cjf('Test $err');
          return true; //见 log2-1。test return true:onError 会执行,后续 then 的 callback 也会被调用,不会抛出 Unhandled Exception
          // return false; //见 log2-2。test reture false:onError 不会执行,后续 then 的 callback 不会被调用,且抛出[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: bbb Error!。
        });

  print_cjf('testFuture 1');

  Future f2 = new Future(() => print_cjf('future 2-1'));
  Future f3 = new Future(() => print_cjf('future 3-1'));

  print_cjf('testFuture 2');

  f3.then((value) => print_cjf('future 3-2' + "---$value"));
  f2.then((value) => print_cjf('future 2-2' + "---$value"));
  f1.then((value) => print_cjf('future 1-4' + "---$value"));

  print_cjf('testFuture end');
}

//输出 log1
I/flutter (22807): 19:5:32:221:cjf---testFuture start //同步执行
I/flutter (22807): 19:5:32:222:cjf---testFuture 1  //同步执行
I/flutter (22807): 19:5:32:223:cjf---testFuture 2 //同步执行
I/flutter (22807): 19:5:32:223:cjf---testFuture end //同步执行

I/flutter (22807): 19:5:32:225:cjf---future 1-1  //异步执行
I/flutter (22807): 19:5:32:226:cjf---future 1-2---aaa //第一步返回的结果
I/flutter (22807): 19:5:32:228:cjf---future 1-3---bbb //第二步返回的结果
I/flutter (22807): 19:5:32:229:cjf---future 1-4---null //then 后调用,却先执行。没有异常,第三步没有返回,默认为 null

I/flutter (22807): 19:5:32:231:cjf---future 2-1
I/flutter (22807): 19:5:32:233:cjf---future 2-2---null //then 后调用,却先执行
I/flutter (22807): 19:5:32:235:cjf---future 3-1
I/flutter (22807): 19:5:32:236:cjf---future 3-2---null //then 先调用,后先执行

//输出 log2-1
I/flutter (22807): 19:6:58:613:cjf---testFuture start
I/flutter (22807): 19:6:58:615:cjf---testFuture 1
I/flutter (22807): 19:6:58:615:cjf---testFuture 2
I/flutter (22807): 19:6:58:615:cjf---testFuture end

I/flutter (22807): 19:6:58:617:cjf---future 1-1
I/flutter (22807): 19:6:58:618:cjf---future 1-2---aaa
I/flutter (22807): 19:6:58:619:cjf---Test bbb Error! //test 返回 ture,onError 会执行
I/flutter (22807): 19:6:58:621:cjf---Caught bbb Error!---#0 testFuture.<anonymous closure> (package:supermarie/testPages/test_isolate.dart:269:9)
I/flutter (22807): <asynchronous suspension>
I/flutter (22807): #1 testFuture.<anonymous closure> (package:supermarie/testPages/test_isolate.dart:276:20)
I/flutter (22807): <asynchronous suspension>
I/flutter (22807): 19:6:58:621:cjf---future 1-4---ccc //onError 捕获异常后,返回结果作为下一步 then 的入参

I/flutter (22807): 19:6:58:622:cjf---future 2-1
I/flutter (22807): 19:6:58:623:cjf---future 2-2---null
I/flutter (22807): 19:6:58:624:cjf---future 3-1
I/flutter (22807): 19:6:58:625:cjf---future 3-2---null

//输出 log2-2
I/flutter (22807): 19:7:48:994:cjf---testFuture start
I/flutter (22807): 19:7:48:996:cjf---testFuture 1
I/flutter (22807): 19:7:48:997:cjf---testFuture 2
I/flutter (22807): 19:7:48:998:cjf---testFuture end

I/flutter (22807): 19:7:49:0:cjf---future 1-1
I/flutter (22807): 19:7:49:2:cjf---future 1-2---aaa
I/flutter (22807): 19:7:49:4:cjf---Test bbb Error! //test 返回 false,onError 不会执行,后续的 then 回调也不会被执行,"future 1-4"不会被打印出来
E/flutter (22807): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: bbb Error!
E/flutter (22807): #0 testFuture.<anonymous closure> (package:supermarie/testPages/test_isolate.dart:269:9)
E/flutter (22807): <asynchronous suspension>
E/flutter (22807): #1 testFuture.<anonymous closure> (package:supermarie/testPages/test_isolate.dart:276:20)
E/flutter (22807): <asynchronous suspension>
E/flutter (22807): 
I/flutter (22807): 19:7:49:7:cjf---future 2-1
I/flutter (22807): 19:7:49:8:cjf---future 2-2---null
I/flutter (22807): 19:7:49:10:cjf---future 3-1
I/flutter (22807): 19:7:49:12:cjf---future 3-2---null

从例子,我们可以看到:

  • 所有

    Future

    创建的任务都是异步执行的,即创建时机和运行时机不同。上例的所有

    log

    中,

    "future 1-1"

    都在

    "testFuture end"

    之后执行;


  • future

    创建异步任务的次序,就是其加入到

    Event queue

    事件队列的次序,也是被异步执行的次序。输出

    log

    可以看到

    "future1-1"

    ,

    "future2-1"

    ,

    "future3-1"

    都是按照创建次序执行;


  • future

    通过

    then

    来获取异步结果,

    then

    支持链式调用。

    then

    注册的

    onValue

    结果回调,会在异步结果返回后立即执行,且后面的

    then

    的入参即是前面

    then

    回调的返回值。输出

    log1



    "future1-2---aaa"

    打印了

    "future1-1"

    中的返回值,而

    "future1-3---bbb"

    打印了

    "future1-2"

    中的返回值;


  • future

    注册

    then

    结果回调的时机,不影响其被回调的时机。见

    log1

    ,

    "future 1-4"

    ,

    "future 2-2"

    ,

    "future 3-2"

    的执行次序,和其调用次序没有关系,只和其依赖的

    future

    的创建次序有关;


  • Future

    通过

    catchError

    注册的

    onError

    回调,受

    test

    回调的返回值影响,我们对比

    log2-1



    log2-2

    看:‍

  • 如果

    test

    回调返回

    true

    ,异常会被

    onError

    处理,并返回值作为下一步

    then

    的入参;

  • 如果

    test

    回调返回

    false

    ,异常不会被

    onError

    处理,还会导致后续

    Then

    回调不被执行,如

    "future 1-4"

    不会被执行到。


Future

的链式调用,很好的解决了调用依赖导致的回调嵌套问题,我们以

Kotlin

中的登入流程为例做对比,源码如下:

// TestCallbackActivity.kt
class TestCallbackActivity extends BaseActivity  {
  // 登入
  fun login(name:String, pwd:String, onSuccess:(uid:String) -> Unit, onError:() -> Unit):Unit {}
  // 获取用户信息, 用户首选 tab
  fun getUserInfo(uid:String, onSuccess:(UserBean) -> Unit, onError:() -> Unit):Unit {}
  // 显示用户信息
  fun showUserInfo(user:UserBean):Unit {}
  // 获取用户首选 tab 动态流
  fun getFeedListByTab(tab:Int, onSuccess:(feedList:List<NewFeedBean>) -> Unit, onError:() -> Unit):Unit { }
  // 显示用户首选 tab 动态流
  fun showFeedList(feedList:List<NewFeedBean>):Unit {}
  // 获取用户第一个动态音乐
  fun getBackgroudMusic(mid:String, onSuccess:(playUrl:String) -> Unit):Unit {}
  // 播放用户动态音乐
  fun playMusic(playUrl:String):Unit {}
    
  override fun onResume() {
        login("name", "pwd", onSuccess = {
            //第一层回调,登入成功
            getUserInfo(it, onSuccess =  {
                //第二层回调,获取用户信息成功
                showUserInfo(it)
                getFeedListByTab(it.mainTab, onSuccess = {
                    //第三层回调,获取用户主页 feed 流成功
                    showFeedList(it)
                    getBackgroudMusic(it[0].mid){
                        //第四层回调,获取 tab 流默认背景音乐成功
                        playMusic(it)
                    }
                }, 
                onError = {})}, 
            onError = {})}, 
        onError = {})
    }
}

因为登入,获取用户信息,获取默认主页

feed

流,和主获取页流背景音乐这几个流程是有强依赖关系的,后面的步骤依赖前一个步骤返回的参数,导致每多一个步骤,就会多一层回调嵌套,非常影响可读性。

同样的逻辑,我们再通过

Flutter



Future

实现,见如下源码。

// testFutureCallback.dart

// 登入
Future<String> login(String name, String pwd) {}
// 获取用户信息, 用户首选 tab
Future<UserBean> getUserInfo(String uid) {}
// 显示用户信息
void showUserInfo(UserBean user) {}
// 获取用户首选 tab 动态流
Future<List<NewFeedBean>> getFeedListByTab(Int tab) {}
// 显示用户首选 tab 动态流
void showFeedList(List<NewFeedBean> feedList) {}
// 获取用户第一个动态音乐
Future<String> getBackgroudMusic(String mid) {}
// 播放用户动态音乐
void playMusic(String playUrl) {}

void testFuture2() async{
  new Future(() {
    return login("name", "pwd");
  }).then((uid) {
    return getUserInfo(uid);
  }).then((user) {
    showUserInfo(user);
    return getFeedListByTab(user.mainTab);
  }).then((feedList) {
    showFeedList(feedList)
    return getBackgroudMusic(feedList[0].mid)
  }).then((playUrl) {
    playMusic(playUrl)
  }).catchError((err, stackTrace) {
    // 根据 err 信息,处理异常
  });
}

可以看到,

Future

通过

then

明确代码块执行的依赖关系,不但消除了多层回调嵌套,也简化了方法的定义和错误处理。当然

Kotlin

中也可以通过协程等方式解决回调嵌套问题。

4.3 async和await



Dart

中还可以通过

async



await

实现异步操作。

async

表示开启一个异步操作,可以返回一个

Future

结果。如果没有返回值,则默认返回一个

Future<void>


async



await

本质上就是

Dart

对异步操作的一个语法糖,可以减少异步调用的嵌套调用。这个语法是在

ES7

标准中推出的,

Dart

中的设计和

JS

相同,是同步风格的异步实现。

我们通过数据加载的示例,来进一步了解

async

,

await

操作:

class _DemoPageState extends State<DemoPage> {
  List widgets = [];
  
  @override
  void initState() {
    super.initState();
    loadData();
    print("initState end");
  }
  
  Future<String> loadData() async {
    print("loadData start");
    Response response = await getNetData(); //位置 1
    setState(() {
      widgets = json.decode(response.body); //位置 2
    });
    return response.body;
  }
  
  Future<Response> getNetData() async {
    String requestURL = 'https://hy.sohu.com/testGetStringList1';
    Client client = Client(); //位置 3
    
    Future<Response> response = client.get(requestURL); //io 异步,后续章节会详细分析
    return response; //位置 4
  }
}

在代码示例中,执行到

loadData

方法时,会同步进入方法内部进行执行,当执行到位置 1 的

await

时,就会停止

loadData

方法内后续的代码的执行,返回到外部调用者

initState

继续执行后面的

print

语句。当位置 1 的

await

有返回后,会从位置 1 处继续执行

response

的赋值操作及后续的

setState

位置 2 的语句。

那么,

await

是怎么做到阻塞当前方法,却不阻塞调用者继续运行的呢?以及,

getNetData

方法中,位置 3 处的代码是被同步执行的,还是被异步执行的呢?

先来分析下第一个问题,我们知道在程序执行过程中,离开当前的调用位置有两种方式:

  • 执行

    return

    返回,当前函数在调用栈中的局部变量、形参等状态会被销毁;

  • 继续调用其他函数,需要保先存当前函数的变量和执行位置,其它函数调用返回后再恢复变量继续执行。保存变量一般有 2 种方式:

  • 一种,会将变量继续保存在栈区,在回到指针指向的离开位置时,会继续从栈中取出变量使用;

  • 另一种,则在执行当前函数时就将变量直接分配到堆区,当再次回到当前位置时,还会继续从堆区中获取变量。

    async

    ,

    await

    就属于这种方式。



Flutter

中,

async

函数也有自己的上下文环境。当执行到

await

时,会保存当前的上下文,并将当前位置标记为待处理任务,并将待处理任务放入当前

Isolate



event

队列中。在每个事件循环时询问这个任务是否满足执行条件,如果需要进行处理,就恢复上下文,从上次离开的位置继续执行。

所以说,

await

并没有开启新的

Isolate

,只是把

await

后的代码封装成待处理任务,放到当前

Isolate

的消息队列中,然后继续运行当前任务。因此

await

做到了不阻塞调用者的执行。

第二个问题,我们通过如下示例进一步说明:

// test_isolate.dart
void testAwait(){ // 测试 await 中断效果
  print_cjf('testAwait start');
  
  fun1();
  fun2();//async 方法,会 await 一个异步结果
  fun3();
  fun4();//async 方法,但是并没有 await 异步结果
  
  print_cjf('testAwait end');
}

void fun1(){
  print_cjf('fun1');
}

void fun2() async{ //声明异步方法 使用 async
  print_cjf('fun2 start');
  await Future.delayed(Duration(seconds: 2)); //使用 await,会异步等待结果返回
  print_cjf('fun2 end');
}

void fun3(){
  print_cjf('fun3');
}

void fun4() async{ //声明异步方法 使用 async
  print_cjf('fun4 start');
  Future.delayed(Duration(seconds: 5)); //没有使用 await, 不会等待结果返回
  print_cjf('fun4 end');
}

// 输出 log 
I/flutter (24288): 16:34:25:354:cjf---testAwait start
I/flutter (24288): 16:34:25:354:cjf---fun1
I/flutter (24288): 16:34:25:354:cjf---fun2 start // await 前的代码被同步执行了
 
I/flutter (24288): 16:34:25:355:cjf---fun3 // 不需要等待 fun2 方法执行完,就被同步执行了
I/flutter (24288): 16:34:25:355:cjf---fun4 start // 被同步执行了
I/flutter (24288): 16:34:25:355:cjf---fun4 end// 同步执行。并没有在 fun4 start 后等 5s 再输出。 但是在 5s 内程序不会退出
 
I/flutter (24288): 16:34:25:355:cjf---testAwait end //fun1,fun2,fun3,fun4 并没有阻塞 testAwait 后续代码的执行
 
I/flutter (24288): 16:34:27:359:cjf---fun2 end//fun2 start 后, 等 2 秒输出 end

这个示例中,我们通过交替调用

async

和非

async

函数,来理解同步调用。我们对比观察

fun2



fun4



log

的输出情况,可以发现:


  • fun2

    中,

    await

    前的代码

    "fun2 start"



    "fun3"

    前执行,所以这行代码是被同步执行的;


  • fun2

    中,

    await Future.delayed

    ,阻塞了

    "fun2 end"

    的同步执行,它会等待

    delay

    2 秒后才被执行,所以最后被执行;


  • fun4

    中,

    Future.delayed

    没有使用

    await

    ,函数执行没有被阻塞,

    "fun4 end"



    "testAwait end"

    前执行,所以是被同步执行的,即

    Future.delayed

    创建的异步任务不会打断当前函数执行。

这个示例中,

Future.delayed

函数本身是同步执行的,它创建一个异步任务,放入当前

Isolate



event

队列后返回。而

await Future.delayed

则需要等待创建的异步任务执行完成后,再恢复当前函数后续代码的执行。

通过

log

分析可知:

await

关键字,把同一个函数分割为同步执行部分和异步执行两部分

await

关键字之前的代码和调用者在同一个任务中同步执行。而没有

await

调用的

async

函数,代码都是同步执行的。

所以在

_DemoPageState

例子中,

getNetData

函数位置 3 的代码会和调用者在同一个任务中被同步执行。而

loadData

函数位置 2 的代码属于待处理任务,会被异步执行。

在 4.1 章节的

testMicroTask

微任务例子中,我们也可以看到

funDelay

异步方法

await

前的

"funDelay start"

代码是被同步执行的。

对比

testFuture

例子中

Future

创建和

then

回调的例子,从

log

中也可以知道,

Future

中创建的异步任务,都是被异步执行的。


await

的使用效果和

Android Kotlin

协程很类似,

launch



async

只是启动协程

job

,

job await

要等待协程结果,才会真正阻塞当前函数后续代码的执行,但是它不阻塞调用线程中其它代码的运行。虽然

Kotlin

协程的异步原理和

Flutter

不同,但通过单线程异步支持大量并发操作的设计思想类同,可以一起对比学习。


Future



async

,

await

两种异步方式,在我们的项目中都有频繁使用,各有优点和适用场景,我们可以通过例子可以更直观的说明:

main( ){
  new Future
  .then(funA())
  .then(funB()); // then 明确表现出了 funB 依赖 funA
}

main( ) async {
  await funA( );
  await funB(); // funB 依赖 funA 的关系不明显
}


Future

能明确方法执行的依赖次序,其它开发者不容易破坏这层依赖逻辑。而

await

没有明确体现方法之间的依赖关系。对于没有通过返回值直接体现依赖的情况下,其他开发者不容易理解到这层依赖,会导致后续代码难以维护。



await

的风格,代码更简洁美观,如果没有依赖关系,更推荐这种风格。

4.4 IO异步

在前面章节中,

testIsolate



testFuture



testAwait

例子中,这些函数和新建异步任务都是在

Root Isolate

中运行的。以及

_DemoPageState

例子中加载网络数据的

getNetData

耗时方法也是在

Root Isolate

中运行的。

但是

Task Runner

章节中明确

UI Runner

负责界面的更新,也运行在

Root Isolate

中。为了避免界面卡顿,必然不能在

Root Isolate

的异步任务中执行耗时任务。

那么像网络请求,文件读取,海量计算,图片编解码等耗时函数的调用,是不是都需要创建新的

Isolate

来处理呢?

我们在实际项目中,会大量使用网络相关耗时

IO

操作。下面通过网络请求过程为例,进一步分析

IO

异步:

//home_page.dart
class KKHomePageState extends BaseThemeState<KKHomePage> with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
  ..........
  
  @override
  void initState() {
    super.initState();
    .......
    if (_login) {
      print_cjf("h02---" + Isolate.current.debugName.toString());
      
      _refresh(); //准备刷新数据
      print_cjf("h022---" + Isolate.current.debugName.toString());
    }
  }
  ......
  
  Future<void> _refresh() async {
    print_cjf("h5---" + Isolate.current.debugName.toString());
    
    await _viewModel.getList(15, 1); // 获取网络数据,等待数据结果。虽然没有直接使用返回值,但是此方法后续的代码中依赖网络数据,所以此处必须 await。但如果后续代码是通过监听的方式来使用网络数据,此处可去掉 await。
    
    print_cjf("h6---" + Isolate.current.debugName.toString());
    homeAllList.currentState?.resetDataAndRefresh();
    ........
  }
}

// subscribe_list_vm.dart
Future<void> getList(int? limit, int? status) async {
  ........

  print_cjf("h7---" + Isolate.current.debugName.toString());
 
  await NetManager.getSubjectSubscribeClient()
  .getMySubscribed(BaseRequestParams().makeSignMap(urlMap: requestMap)) //获取用户订阅的主题
  .then((value) {
    print_cjf("h8---" + Isolate.current.debugName.toString());
    ......
  }
  ........
}

// log 输出:
I/flutter (13129): 15:32:23:235:cjf---h02---main // main_page 中的 initstate,调用_refresh 之前。同步执行。
I/flutter (13129): 15:32:23:236:cjf---h5---main // 进入 _refresh() async {}方法,调用_viewModel.getList 之前。同步执行。
I/flutter (13129): 15:32:23:238:cjf---h7---main // 同步执行。await _viewModel.getList(15, 1); 进入到 subscribe_list_vm 中的 getList 方法。同步执行。
I/flutter (13129): 15:32:23:239:cjf---h022---main // 同步执行。main_page 中的 initstate,调用_refresh 之后。同步执行。

I/flutter (13129): 15:32:23:742:cjf---h8---main // await NetManager.getSubjectSubscribeClient().getMySubscribed 方法返回。异步步执行。
I/flutter (13129): 15:32:23:760:cjf---h6---main // 进入 _refresh() async {}方法,调用_viewModel.getList 之后。异步执行。



log

可以看出网络请求

getList async

方法的代码也是在 Root Isolate 执行的,并没有启动新的

Isolate


await

等待

aysnc

方法结果时,会产生

Isolate

内的异步调度,即把待处理任务

h6



h8

放到当前

Isolate

的消息队列中,然后继续运行当前任务打印

"h022---main"

那么

NetManager.getSubjectSubscribeClient

通过

retrofit

获取网络数据的耗时

IO

也是在

Root Isolate

中执行的吗?

我们通过网络限速,使得获取网络数据的接口 5s 后返回结果。验证发现

KKHomePage

在这 5s 中可以流畅地进行操作,且能打印出

Root Isolate

中其它异步任务执行的

log

,这说明网络耗时

IO

没有在

Root Isolate

中执行,这是什么原因呢?

这是因为网络请求过程中,当运行到系统

IO

接口时,会将

IO

任务交给系统处理(属于系统的线程),并将异步任务加入到事件队列,然后

Root Isolate

回到事件循环,获取事件队列中的下一个任务进行处理。


Flutter

文档中,没有明确哪些接口属于系统

IO

接口,但是通过

Dart SDK

的源码跟踪,我们可以发现,网络请求

HttpClient

最后的

IO

操作是在

dart

虚拟机中的

native socket

中实现的,而

socket

中使用了线程池来管理线程,并把

IO Task

分配到各子线程中运行。

所以,对于有系统

IO

操作的耗时任务,如网络请求,文件读取等,可以使用

await

,等待耗时

IO

操作的异步结果。

而执行大量高

CPU

的运算类耗时任务,如耗时计算,编解码等,即便使用

await

,但所有代码都运行在

Root Isolate

中,所以仍然会导致

Root Isolate

没法及时处理其他异步任务,从而导致

UI

卡顿。所以官方推荐高

CPU

耗时任务,应该使用新的

Isolate

去执行,如前面章节的

testCompute

例子所示。

通过上面的分析,我们就可以很好地理解,为什么官方只说

Isolate

不支持高

CPU

操作,而没有说不支持耗时

IO

操作了。

开辟新的

Isolate

的成本比较高,所以对于不太耗时的任务,还是建议用

Future



await

的方式。那么,什么样的任务算高

CPU

的耗时任务呢?这个没有绝对标准,建议毫秒级的任务用

Future



await

,而百毫秒级的任务,比如图片处理,数据加密等,开启新的

Isolate

4.5 Js异步


Flutter

中的线程异步和浏览器中异步原理是一样的,而且

async



await

语法糖也是在

ES7

标准中推出的,通过对比,我们可以进一步对

Flutter

的异步加深了解。



JavaScript

的世界中,所有代码都是单线程执行的,即通常情况下,

Js

代码不必考虑多线程。

JS

中,异步的目的是为了提高

CPU

的执行效率,提高用户体验。它和同步一样,运行在同一线程中。它和同步的差别在于,同一流水线上各个代码片段的执行顺序不同。

下面我们通过示例来看一下

JS

中的异步:

// test.js
async function fun1() {
  var vConsole = new VConsole();
  console.log('cjf--- fun1---1')
  
  var a = 123
  console.log('cjf--- fun1---2')
  
  var b = await fun2() //等待 fun2 的异步结果
  console.log('cjf--- fun1---3')
  
  var c = b
  console.log('cjf--- fun1---4')
  return 1;
}

async function fun2() {
  console.log('cjf--- fun2---1')
  
  var a = 123
  console.log('cjf--- fun2---2')
  
  var b = await fun3() //等待 fun3 的异步结果
  console.log('cjf--- fun2---3')
  
  var c = b
  console.log('cjf--- fun2---4')
}

async function fun3() {
  console.log('cjf--- fun3---1')
  
  var a = 123
  console.log('cjf--- fun3---2')
  
  var b = await makeRequest()  //等待网络请求的异步结果
  console.log('cjf--- fun3---3')
  
  var c = b
  console.log('cjf--- fun4---4')
  return 3;
}

function makeRequest() {
  console.log('cjf--- makeRequest---1')
  
  httpRequest = new XMLHttpRequest();
  if (!httpRequest) {
    alert('Giving up :( Cannot create an XMLHTTP instance');
    return false;
  }
  
  httpRequest.onreadystatechange = alertContents; // 注册异步回调
  httpRequest.open('GET', 'test.html'); // 耗时 IO 接口,会切换到引擎新线程
  console.log('cjf--- makeRequest---2')
  
  httpRequest.send(); // 耗时 IO 接口,会切换到引擎新线程
  console.log('cjf--- makeRequest---3')
  return 4
}

function alertContents() { // 数据 ready 回调
  console.log('cjf--- alertContents---1---' + httpRequest.readyState)
  if (httpRequest.readyState === XMLHttpRequest.DONE) {
    console.log('cjf--- alertContents---2')
    if (httpRequest.status === 200) {
      alert(httpRequest.responseText);
      console.log('cjf--- alertContents---3---' + httpRequest.responseText)
    } else {
      alert('There was a problem with the request.');
      console.log('cjf--- alertContents---4---a problem with the request.')
    }
  }
}

fun1() //调用方法。

// log 输出
cjf--- fun1---1
cjf--- fun1---2
cjf--- fun2---1
cjf--- fun2---2
cjf--- fun3---1
cjf--- fun3---2 //同步执行

cjf--- makeRequest---1 //同步执行
cjf--- alertContents---1---1 //httpRequest.open 会回调 alertContents
cjf--- makeRequest---2 //异步执行
cjf--- makeRequest---3 //异步执行

cjf--- fun3---3 //异步执行
cjf--- fun3---4 //异步执行
cjf--- fun2---3 //异步执行
cjf--- fun2---4 //异步执行
cjf--- fun1---3 //异步执行
cjf--- fun1---4 //异步执行

cjf--- alertContents---1---4 //httpRequest.send 会异步回调 alertContents
cjf--- alertContents---2 //httpRequest.send 会异步回调 alertContents
cjf--- alertContents---4---a problem with the request. //httpRequest.send 会异步回调 alertContents

从上述例子可以看出,

Func1



Func2



Func3

中的

a

赋值操作都是同步执行的,

b



c

的赋值操作是异步执行的。

在浏览器运行

await

时, 会一层一层运行到

游览器引擎新建线程接口

(比如

Ajax

请求接口,

XMLHttpRequest.send

),调用系统

IO

接口前的代码片段是被同步执行的,之后的代码片段则需要等到异步结果返回后,才能继续执行。

所以

await

实际上并不是异步执行的 “分界线”,即

await

前面的代码同步执行,

await

后面的代码异步执行。因为

await

后面的异步函数代码中的部分内容,也可能被同步执行,代码会一直运行到

系统


IO


接口

处,才算到了分界线。但是从执行的结果来看,

await

确实达到了 “阻塞等待” 的效果。

在使用时,我们并不关心异步函数中的部分代码被同步执行了还是被异步执行了,我们关心的是

await

的异步数据必须返回,才能再继续执行下面的代码。但对原理的深入理解,可以提高我们对系统设计思想的运用能力。

小结

通过上面的学习,我们对

Flutter

中的线程模型和单线程异步原理有了更深入的理解,开发时也能更好更安全地运用

Flutter



Isolate



Future



await

我们最后总结下文章的核心内容:


  • Isolate



    Dart

    平台对线程的实现方案。和普通

    Thread

    不同,

    Isolate

    拥有独立的内存,由线程和独立内存构成。

    Isolate

    线程之间并不存在资源抢夺的问题,所以也不需要线程同步机制;

  • 简单耗时任务,可以使用简版

    Isolate



    compute


  • Isolate

    通过事件循环和消息队列来实现单线程异步并发,这比多线程异步并发要轻便;

  • 可以通过

    Future

    进行单线程异步并。

    Future

    异步的特点是提供了链式调用,可以解决回调地狱,异常捕获和代码执行依赖等问题;

  • 也可以通过

    async



    await

    实现单线程异步并发,它是同步风格的异步操作,比

    Future

    更简洁美观;


  • Root Isolate

    支持

    IO

    耗时操作,但不支持高

    CPU

    耗时操作。



版权声明:本文为SOHU_TECH原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。