Flutter 异步编程原理(下)

简介: Flutter 异步编程原理(下)

1)UI Task Runner Thread(Dart Runner)


UI Task Runner 用于执行 Dart root isolate 代码(isolate 我们后面会讲到,姑且先简单理解为 Dart VM 里面的线程)。Root isolate 比较特殊,它绑定了不少 Flutter 需要的函数方法,以便进行渲染相关操作。对于每一帧,引擎要做的事情有:

  • Root isolate 通知 Flutter Engine 有帧需要渲染。
  • Flutter Engine 通知平台,需要在下一个 vsync 的时候得到通知。
  • 平台等待下一个 vsync。
  • 对创建的对象和 Widgets 进行 Layout 并生成一个 Layer Tree,这个 Tree 马上被提交给 Flutter Engine。当前阶段没有进行任何光栅化,这个步骤仅是生成了对需要绘制内容的描述。
  • 创建或者更新 Tree,这个 Tree 包含了用于屏幕上显示 Widgets 的语义信息。这个东西主要用于平台相关的辅助 Accessibility 元素的配置和渲染。


2)GPU Task Runner


GPU Task Runner 主要用于执行设备 GPU 的指令。UI Task Runner 创建的 Layer Tree 是跨平台的,它不关心到底由谁来完成绘制。GPU Task Runner 负责将 Layer Tree 提供的信息转化为平台可执行的 GPU 指令。GPU Task Runner 同时负责绘制所需要的 GPU 资源的管理。资源主要包括平台 Framebuffer,Surface,Texture和Buffers 等。


一般来说 UI Runner 和 GPU Runner 跑在不同的线程。GPU Runner 会根据目前帧执行的进度去向 UI Runner 要求下一帧的数据,在任务繁重的时候可能会告诉 UI Runner 延迟任务。这种调度机制确保 GPU Runner 不至于过载,同时也避免了 UI Runner 不必要的消耗。

建议为每一个 Engine 实例都新建一个专用的 GPU Runner 线程。


3)IO Task Runner


前面讨论的几个 Runner 对于执行流畅度有比较高的要求。Platform Runner 过载可能导致系统 WatchDog 强杀,UI 和 GPU Runner 过载则可能导致 Flutter 应用的卡顿。但是 GPU 线程的一些必要操作,例如 IO,放到哪里执行呢?答案正是 IO Runner。


IO Runner 的主要功能是从图片存储(比如磁盘)中读取压缩的图片格式,将图片数据进行处理为 GPU Runner 的渲染做好准备。IO Runner 首先要读取压缩的图片二进制数据(比如 PNG,JPEG),将其解压转换成 GPU 能够处理的格式然后将数据上传到 GPU。

获取诸如 ui.Image 这样的资源只有通过 async call 去调用,当调用发生的时候 Flutter Framework 告诉 IO Runner 进行加载的异步操作。


IO Runner 直接决定了图片和其它一些资源加载的延迟间接影响性能。所以建议为 IO Runner 创建一个专用的线程。


4)Platform Task Runner


Flutter Engine 的主 Task Runner,类似于 Android Main Thread 或者 iOS 的 Main Thread。但是需要注意他们还是有区别的。


一般来说,一个 Flutter 应用启动的时候会创建一个 Engine 实例,Engine创建的时候会创建一个线程供 Platform Runner 使用。


跟 Flutter Engine 的所有交互(接口调用)必须在 Platform Thread 进行,否则可能导致无法预期的异常。这跟 iOS UI 相关的操作都必须在主线程进行相类似。需要注意的是在 Flutter Engine 中有很多模块都是非线程安全的。


阻塞 Platform Thread 不会直接导致 Flutter 应用的卡顿(跟 iOS Android 主线程不同)。尽管如此,也不建议在这个 Runner 执行繁重的操作,长时间卡住 Platform Thread 应用有可能会被系统 Watchdog 强杀。


各个平台的线程配置


1.iOS为每个引擎实例的UI,GPU和IO任务运行程序创建专用线程。所有引擎实例共享相同的Platform Thread和Platform Task Runner。2.Android为每个引擎实例的UI,GPU和IO任务运行程序创建专用线程。所有引擎实例共享相同的Platform Thread和Platform Task Runner。3.Fuchsia每一个Engine实例都为UI,GPU,IO,Platform Runner创建各自新的线程。4.Flutter Tester (used by flutter test)UI,GPU,IO和Platform任务运行器使用相同的主线程。


创建 isolate:使用 spawnUri


spawnUri 方法有三个必须的参数

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

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

import 'dart:isolate';
void main() {
  print("main isolate start");
  create_isolate();
  print("main isolate stop");
}
// 创建一个新的 isolate
create_isolate() async {
  ReceivePort rp = ReceivePort();
  ///创建发送端
  SendPort port1 = rp.sendPort;
  ///创建新的 isolate ,并传递了发送端口。
  Isolate newIsolate = await Isolate.spawnUri(Uri(path: "./other_task.dart"), ["hello, isolate", "this is args"], port1);
  SendPort port2;
  rp.listen((message){
    print("main isolate message: $message");
    if (message[0] == 0){
      port2 = message[1];
    }else{
      port2?.send([1,"这条信息是 main isolate 发送的"]);
    }
  });
  // 可以在适当的时候,调用以下方法杀死创建的 isolate
  // newIsolate.kill(priority: Isolate.immediate);
}


在主 isolate 文件的同级路径下,新建一个 other_task.dart ,内容如下:

import 'dart:isolate';
import 'dart:io';
///接收到了发送端口。
void main(args, SendPort port1) {
  print("isolate_1 start");
  print("isolate_1 args: $args");
  ReceivePort receivePort = ReceivePort();
  SendPort port2 = receivePort.sendPort;
  receivePort.listen((message){
    print("isolate_1 message: $message");
  });
  // 将当前 isolate 中创建的SendPort发送到主 isolate中用于通信
  port1.send([0, port2]);
  // 模拟耗时5秒
  sleep(Duration(seconds:5));
  port1.send([1, "isolate_1 任务完成"]);
  print("isolate_1 stop");
}


运行主 isolate,输出结果:

main isolate start
main isolate stop
isolate_1 start
isolate_1 args: [hello, isolate, this is args]
main isolate message: [0, SendPort]
isolate_1 stop
main isolate message: [1, isolate_1 任务完成]
isolate_1 message: [1, 这条信息是 main isolate 发送的]
复制代码

isolate 之间的通信时通过 ReceivePort 来完成的,ReceivePort 可以理解为一根水管,消息的传递方向时固定的,把这个水管的发送端发送给对方, 对方就能通过这个水管发送消息。


创建 isolate:通过 spawn


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

  • 第一个是需要运行在新 Isolate 的耗时函数
  • 第二个是动态消息,该参数通常用于传送主 Isolate 的 SendPort 对象
  • (可选)第三个是命名方法,这个值可以做表意使用,但他不是唯一的
import 'dart:isolate';
import 'dart:io';
void main() {
  print("主程序开始");
  create_isolate();
  print("主程序结束");
}
// 创建一个新的 isolate
create_isolate() async{
  ReceivePort rp = new ReceivePort();
  SendPort port1 = rp.sendPort;
  Isolate newIsolate = await Isolate.spawn(doWork, port1);
  SendPort port2;
  rp.listen((message){
    print("main isolate message: $message");
    if (message[0] == 0){
      port2 = message[1];
    }else{
      port2?.send([1,"这条信息是 main isolate 发送的"]);
    }
  });
}
// 处理耗时任务
void doWork(SendPort port1){
  print("new isolate start");
  ReceivePort rp2 = new ReceivePort();
  SendPort port2 = rp2.sendPort;
  rp2.listen((message){
    print("doWork message: $message");
  });
  // 将新isolate中创建的SendPort发送到主isolate中用于通信
  port1.send([0, port2]);
  // 模拟耗时5秒
  sleep(Duration(seconds:5));
  port1.send([1, "doWork 任务完成"]);
  print("new isolate end");
}


结果:

主程序开始
主程序结束
new isolate start
main isolate message: [0, SendPort]
new isolate end
main isolate message: [1, doWork 任务完成]
doWork message: [1, 这条信息是 main isolate 发送的]


创建 isolate:Compute 函数


Compute 函数对 isolate 的创建和底层的消息传递进行了封装,使得我们不必关系底层的实现,只需要关注功能实现。

首先我们需要:

  1. 一个函数:必须是顶级函数或静态函数
  2. 一个参数:这个参数是上面的函数定义入参(函数没有参数的话就没有)
  3. (可选)debugLabel:字典类参数,命名线程


比如计算斐波那契数列:

void main() async{
  // 调用compute函数,compute函数的参数就是想要在isolate里运行的函数,和这个函数需要的参数
  print(await compute(syncFibonacci, 20));
  runApp(MyApp());
}
int syncFibonacci(int n){
  return n < 2 ? n : syncFibonacci(n-2) + syncFibonacci(n-1);
}


运行后的结果如下:

flutter: 6765


相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
目录
相关文章
|
1月前
|
开发者 容器
Flutter&鸿蒙next 布局架构原理详解
本文详细介绍了 Flutter 中的主要布局方式,包括 Row、Column、Stack、Container、ListView 和 GridView 等布局组件的架构原理及使用场景。通过了解这些布局 Widget 的基本概念、关键属性和布局原理,开发者可以更高效地构建复杂的用户界面。此外,文章还提供了布局优化技巧,帮助提升应用性能。
103 4
|
1月前
|
存储 Dart 前端开发
flutter鸿蒙版本mvvm架构思想原理
在Flutter中实现MVVM架构,旨在将UI与业务逻辑分离,提升代码可维护性和可读性。本文介绍了MVVM的整体架构,包括Model、View和ViewModel的职责,以及各文件的详细实现。通过`main.dart`、`CounterViewModel.dart`、`MyHomePage.dart`和`Model.dart`的具体代码,展示了如何使用Provider进行状态管理,实现数据绑定和响应式设计。MVVM架构的分离关注点、数据绑定和可维护性特点,使得开发更加高效和整洁。
163 3
|
2月前
动画控制器在 Flutter 中的工作原理
【10月更文挑战第18天】总的来说,动画控制器 `AnimationController` 在 Flutter 中起着关键的作用,它通过控制动画的数值、速度、节奏和状态,实现了丰富多彩的动画效果。理解它的工作原理对于我们在 Flutter 中创建各种精彩的动画是非常重要的。
|
6月前
|
存储 开发框架 JavaScript
深入探讨Flutter中动态UI构建的原理、方法以及数据驱动视图的实现技巧
【6月更文挑战第11天】Flutter是高效的跨平台移动开发框架,以其热重载、高性能渲染和丰富组件库著称。本文探讨了Flutter中动态UI构建原理与数据驱动视图的实现。动态UI基于Widget树模型,状态变化触发UI更新。状态管理是关键,Flutter提供StatefulWidget、Provider、Redux等方式。使用ListView等可滚动组件和StreamBuilder等流式组件实现数据驱动视图的自动更新。响应式布局确保UI在不同设备上的适应性。Flutter为开发者构建动态、用户友好的界面提供了强大支持。
113 2
|
2月前
|
容器
Flutter&鸿蒙next 布局架构原理详解
Flutter&鸿蒙next 布局架构原理详解
|
2月前
|
机器学习/深度学习 开发框架 Dart
Flutter asynchronous 异步编程技巧
本文深入探讨了Flutter中的异步编程技巧,包括Future、Microtask及并发处理的最佳实践。文章详细讲解了Future.wait、FutureBuilder和Microtask的应用,帮助开发者提升应用性能。通过实例演示了如何利用Future.wait实现并发执行,FutureBuilder简化UI构建,以及Microtask的高优先级执行特性。适合希望优化Flutter应用异步性能的开发者阅读。
|
4月前
|
Dart 前端开发 JavaScript
Flutter&Dart-异步编程Future、Stream极速入门
Flutter&Dart-异步编程Future、Stream极速入门
89 4
Flutter&Dart-异步编程Future、Stream极速入门
|
5月前
|
Dart JavaScript Java
flutter 架构、渲染原理、家族
flutter 架构、渲染原理、家族
97 3
|
5月前
|
Dart
flutter 之 Dart 异步编程【详解】
flutter 之 Dart 异步编程【详解】
50 0
|
7月前
|
Android开发
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析