flutter 单线程异步 及 isolate 使用过程遇到的问题

Posted 一叶飘舟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了flutter 单线程异步 及 isolate 使用过程遇到的问题相关的知识,希望对你有一定的参考价值。

遇到两个问题:

  1. 官方 子 isolate 内不能使用插件. 解决方案: 使用 isolate_handler / flutter_isolate 代替官方 isolate 参考链接
  2. 子 isolate 处理完耗时操作, 传递结果给主 isolate 的方法: void send(Object? message); 耗时严重, 达秒级, 顿了一下, 页面才刷新的感觉( 未解决 )

Dart 单线程异步怎么实现的?

Dart 是单线程语言. Flutter 依赖于Dart
Dart的 “线程” 是指 isolate.
isolate 与线程的区别: isolate 之间是内存独立的, isolateA 不能访问isolateB的内存数据.
Future 可以实现异步.异步但串行非并发执行任务
单线程并不是说 Dart 只有一个线程! Dart可以创建子 isolate 实现多线程.


loop内有两个FIFO队列

  1. microask queue
  2. event queue (里面就是Future任务)

前者执行优先级高于后者, 如图


future / async / await

 官方async-await描述

1. future 是Future 的实例对象, future 代表一个异步操作的结果, 它有两种状态: 未完成和已完成.
2. await 可以修饰函数/, 如果该函数返回值为Future 类型, 则会阻塞当前流程, 等待被修饰的函数执行完成后继续执行后续代码. 函数内使用 await 的前提是该函数是一个 async 函数. 也就是说 await 要和 async 配对使用.
3. 但是 async 函数内可以没有 await 关键字.

总结: await future 会阻塞流程

一: 关于async 函数:

函数内有阻塞流程(await future)的操作, 则函数会返回一个 future 实例 
(1) 如果该函数本身有返回值, 比如类型为T, 则返回类型为Future<T.>
(2) 如果该函数本身没有返回值, 则返回类型为 Future<void.>

比如下面的 funcTest 函数会返回 Future<void.>

funcTest() async 
	await Future(()  
    	print('funcTest end');
 	 );

二 async 和 await 的执行流程

一个async函数同步运行直到第一个 await关键字。这意味着在async函数体内,第一个await关键字之前的所有同步代码都会立即执行。

版本说明: 在 Dart 2.0 之前,async函数立即返回,不执行async函数体内的任何代码。

例子:

void main() async 
	print('main begin'); 
	funcB();
	print('main end'); 

funcB() async 
	print('funcB begin'); 
	await Future.delayed(Duration(seconds: 2), ()
		print('funcB delayed over'); 
	); 
	print('funcB end'); 

funcB 虽然是异步函数, 但是在main函数执行到funcB时并不会跳过去打印’main end’, 而是继续执行funcB , 直到遇到funcB的第一个await关键字后返回.

打印结果:
 

main begin
funcB begin
main end
funcB delayed over
funcB end

Future 实现异步

官方event-loop描述

1. 立刻把任务加入 event queue, 用 Future():
// Adds a task to the event queue.
new Future(() 
// …code goes here…
);
2. some time 后把任务加入(并非执行!) event queue用 Future.delayed():
// After a one-second delay, adds a task to the event queue.
new Future.delayed(const Duration(seconds:1), () 
// …code goes here…
);

注意: Future 或 Future.delayed 的返回值是Future<T.>类型还是 Future<void.> 类型, 取决于执行函数有无返回值, 有返回值就是前者, 无返回值就是后者; 可以把执行函数理解成上文的异步函数
执行main函数:

  1. main begin
  2. funcA 是同步函数, 正常执行
    1. funcA
  3. 这时到了funcB, 进入函数内同步执行, 直到遇到第一个await返回. 同时把函数内Future.delay添加到 event queue 队尾. 并立刻往下执行 funcC
    1. funcB begin
  4. funcC 是 async 函数, 虽然调用时使用了await 关键字, 但是 funcC 函数内部没有阻塞流程的代码, 不会返回future. 这里 await funcC(); 等同于 funcC();
    1. funcC begin
    2. 2s后把执行函数加入 event loop
    3.  funcC end
  5. 5. funcD 与 funcC 不同的是, 方法内的 Future.delayed 调用时使用了 await, 则funcD 返回一个 future, 且会阻塞funcD 函数体, 等Future.delayed 执行函数完成才会打印 ‘funcD end’,又因为 main 函数 await funcD(); 所以要等 funcD 函数全部执行完成, 才会打印 'main end’
    1. funcD begin
    2. 2s后把执行函数加入 event loop
    3. Future.delay 执行函数完成 打印 funcD end
  6. main 函数处于等待 funcD的状态, 开始loop, 因为我们没有往microtask queue 添加任务, 所以串行执行(非并发!!!) event queue
    1. 此时 event queue 的任务:  
      funcB.Future.delayed
      funcC.Future.delayed
      funcD.Future.delayed

FIFO原则

(1) funcB delayed over
(2) funcB end 这里我的理解是, ‘funcB end’ 被放在了’funcB delayed over’的 then 回调里, ‘funcB delayed over’ 完成后立刻执行 ‘funcB end’ 然后才 loop下一个任务
(3) funcC delayed over
(4) funcD delayed over
(5) mainEnd

void main() async 
	print('main begin'); 
	funcA();
	funcB();
	await funcC();
	await funcD();
	print('main end'); 

funcA() 
	print('funcA'); 

funcB() async 
	print('funcB begin'); 
	// 2s后把执行函数加入 event loop, 且执行函数变成完成状态才会执行下面的 'funcB end'
	await Future.delayed(Duration(seconds: 2), ()
		print('funcB delayed over'); 
	); 
	print('funcB end'); 

funcC() async 
	print('funcC begin'); 
	// 2s后把执行函数加入 event loop
	Future.delayed(Duration(seconds: 2), ()
		print('funcC delayed over'); 
	); 
	print('funcC end'); 

funcD() async 
	print('funcD begin'); 
	// 2s后把执行函数加入 event loop, 且执行函数变成完成状态才会执行下面的 'funcD end'
	await Future.delayed(Duration(seconds: 2), ()
		print('funcD delayed over'); 
	); 
	print('funcD end'); 

isolate

我们平时写的代码就运行在flutter创建好的UI线程. 当运行耗时代码时页面会有卡顿感, 就是掉帧了.
解决方案: 开辟新线程处理耗时操作, 把处理结果传给 UI 线程刷新页面.
Dart 开辟新线程的方式是使用 isolate.
isolate 与普通线程的区别在于: isolate之间是内存隔离的!!!.
比如: isolate A 内不能访问 isolate B 的变量

此时好像就结束了, 但是! 官方isolate有一个致命局限性…
我们自己创建的isolate内是没办法使用插件的. 原因是Platform-Channel的通信只能在主isolate内执行
在Stack Overflow 发现两个插件, 可以完美解决这个问题:

  1. isolate_handler
  2. flutter_isolate

但是当我感觉终于要看到胜利的曙光时我发现,
这个方法耗时达秒级(把子isolate执行完毕的结果发送给主isolate)

abstract class SendPort implements Capability 
  /// Sends an asynchronous [message] through this send port, to its
  /// corresponding `ReceivePort`.
  ///
  /// The content of [message] can be: primitive values
  /// (null, num, bool, double, String), instances of [SendPort],
  /// and lists and maps whose elements are any of these.
  /// List and maps are also allowed to contain cyclic references.
  ///
  /// In the special circumstances when two isolates share the same code and are
  /// running in the same process (e.g. isolates created via [Isolate.spawn]),
  /// it is also possible to send object instances (which would be copied in the
  /// process). This is currently only supported by the
  /// [Dart Native](https://dart.dev/platforms#dart-native-vm-jit-and-aot)
  /// platform.
  ///
  /// The send happens immediately and doesn't block.  The corresponding receive
  /// port can receive the message as soon as its isolate's event loop is ready
  /// to deliver it, independently of what the sending isolate is doing.
  
	void send(Object? message);


如果这样, isolate的使用场景就变得很少了, 也只能后台任务不需要刷新页面的情况下使用了

参考: futures-isolates-event-loop
 

以上是关于flutter 单线程异步 及 isolate 使用过程遇到的问题的主要内容,如果未能解决你的问题,请参考以下文章

Flutter之异步操作async原理Future本质

Flutter之异步操作async原理Future本质

Flutter之异步操作async原理Future本质

Flutter之异步操作async原理Future本质

Flutter异步编程详解

Dart 异步编程之 Isolate 和事件循环