Flutter(二十二)——异步编程

举报
择城终老 发表于 2021/07/26 23:00:10 2021/07/26
【摘要】 本文目录 前言isolateevent loop线程模型与isolate创建单独的isolateStream事件流 前言 说到网络,就一定会提到异步编程。对于涉及网络的操作,在客户端的开发中都是通过异步实现的。在Flutter里,异步是用Future来修饰的,并且运行在event loop里。 Flutter的异步特性和Android的Looper...

前言

说到网络,就一定会提到异步编程。对于涉及网络的操作,在客户端的开发中都是通过异步实现的。在Flutter里,异步是用Future来修饰的,并且运行在event loop里。

Flutter的异步特性和Android的Looper以及前端的event loop有点像,都是不断地从事件队列里获取事件然后运行,并通过异步操作有效防止一些耗时任务对主UI线程地影响。

isolate

Flutter中很重要的一个概念就是isolate,它是通过Flutter Engine层面的一个线程来实现的,而实现isolate的线程又是由Flutter管理和创建的。除了isolate所在的线程以外,还有其他的线程,它们跟Flutter的线程模型(Threading Mode)有关。在我们介绍完isolate的基本用法和概念之后,后面会专门由一节介绍Flutter Engine层线程模型相关知识。

所有的Dart代码都是在isolate上运行的。通常情况下,我们的应用都是运作在main isolate中的,在必要时我们可以通过相关的API创建新的isolate,以便更好的利用CPU的特性,并以此提高效率。需要注意一点,多个isolate无法共享内存,必须通过相关的API通信才可以。

event loop

另一个比较重要的概念是event loop。学过过JS前端知识的一定对event loop有所了解,理解它并不困难,而且Flutter简单一些,下面是它的原理图:
流程图(1)运行App并执行main方法

(2)开始并优先处理microtask queue,直到队列为空。

(3)当microtask queue为空后,开始处理event queue。如果event queue里面有event,则执行,每执行一条在判断此时新的microtask queue是否为空,并且每一次只取出一个来执行。可以这样理解,在处理所有event之前我们会做一些事情,并且会把这些事情放在microtask queue中。

(4)microtask queue和event queue都为空,则App可以正常退出。

特别注意:当处理microtask queue时,event queue是会被阻塞的。所以microtask queue中应避免任务太多或长时间处理,否则将导致App的绘制和交互等行为被卡住。所以,绘制和交互等应该作为event存放在event queue中,这样更合适。

我们直接来看一个例子,代码如下:

_loopDemo(){ print("开始"); scheduleMicrotask(()=>print("microtask队列1")); new Future.delayed(new Duration(seconds: 1),()=>print("future队列1 delayed")); new Future(()=>print("future队列2")) .then((_)=>print("future队列2 then1")) .then((_){ print("future队列2 then 2"); scheduleMicrotask(()=>print("future队列2 then2中microtask队列")); }).then((_)=>print("future队列2 then3")) .then((_)=>print("future队列2 then4")); scheduleMicrotask(()=>print("microtask队列2")); new Future(()=>print("future队列3")) .then((_)=>new Future(()=>print("future队列3 then1 future"))) .then((_)=>print("future队列3 then2")) .then((_)=>print("future队列3 then3")); new Future(()=>print("future队列4")) .then((_){ new Future(()=>print("future队列4 then1 future")); }).then((_)=>print("future队列4 then2")) .then((_)=>print("future队列4 then3")); scheduleMicrotask(()=>print("microtask队列3")); print("结束");

  
 
  • 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

这里,scheduleMicrotask代表流程图中的microtask queue,future代表event queue,控制台打印的结果如下:
打印这段代码有几点需要注意:

(1)Future.delayed表示延迟执行,在设置的延迟时间到了之后,才会被放在event loop队列尾部。

(2)Future.then里的任务不会添加到event queue中,要保证异步任务的执行顺序就一定要用then。

线程模型与isolate

上面已经介绍,isolate是通过Flutter Engine层面的一个线程来实现的,而实现isolate的线程是由Flutter管理和创建的。其实,Flutter Engine线程的创建和管理是由embedder(嵌入层)负责的,embeder是平台引擎移植的中间代码。下面就是Flutter Engine的运行架构:
dartVM可以看到它给我们提供了4种task Runner:

(1)Platform Task Runner

Platform Task Runner是Flutter Engine的主Task Runner,它不仅可以处理与Engine的交互,还可以处理来自Native(Android/IOS)平台的交互。平台的API都只能在主线程中被调用。每一个Flutter应用启动的时间都会创建一个Engine实例,Engine创建的时候都会创建一个Platform Thread 供Platform Task Runner使用。即使Platform Thread被阻塞,也不会直接导致Flutter应用的卡顿。尽管如此,也不建议在这个Runner里执行如此繁重的操作,长时间卡住Platform Thread,应用有可能会被系统的Watchdog强行终止。

(2)UI Task Runner

UI Task Runner不能想当然的理解为像Android那样是运行在主线程的,它其实是运行在线程对应到平台的线程上的,属于子线程。Root isolate在引擎启动时绑定了不少Flutter所需要的方法,从而使其具备调度/提交/渲染帧的能力。在Root isolate通知Flutter Engine有帧需要被渲染后,渲染时就会生成Layer Tree并交给Flutter Engine。此时,仅生成了需要描绘的内容,然后才创建和更新Layer Tree,该Tree最终决定什么内容会在屏幕上被绘制,因此 UI Task Runner如果过载会导致卡顿。

isolate可以理解为单线程,如果运算量大,可以考虑采用独立的isolate,单独创建的isolate(非Root isolate)不支持绑定Flutter的功能,也不能调用,只能做数据运算。单独创建的isolate的生命周期会受Root isolate控制,只要 Root isolate停止了,那么其他的isolate也会停止。isolate运行的线程是Dart VM里的线程池提供的。

UI Task Runner还可以处理来自Native Plugins的消息,timers,microtask,异步I/O操作。

(3)GPU Task Runner

GPU Task Runner被用于执行与设备GPU相关的调用,它可以将GPU Task Runner生成的Layer Tree所提供的信息转换为实际的GPU指令。GPU Task Runner的运行线程对应着平台的子线程。UI Task Runner和GPU Task Runner在不同的线程上运行。GPU Runner会根据目前帧被执行的进度向UI Task Runner要求下一帧的数据。在任务繁重的时候,UI Task Runner会延迟任务进程。这种调度机制确保了GPU Task Runner不至于出现过载现象,同时避免了UI Task Runner不必要的消耗。如果在线程中耗时太久的话,就会造成Flutter应用的卡顿,因此在GPU Task Runner中,尽量不要做耗时的任务。例如,加载图片的同时读取图片的数据,就不应该在GPU Task Runner中运行,而应该放在IO Task Runner中进行。

(4)IO Task Runner

IO Task Runner的运行线程也对应这平台的子线程。当UI和GPU Task Runner都出现了过载的情况时,会导致Flutter应用卡顿。IO Task Runner就会负责做一些预先处理的读取操作,然后再上报给GPU Task Runner,且只有GPU Task Runner可以访问到GPU。简单点说就是,IO Task Runner是GPU Task Runner的助手,可以减少GPU Task Runner额外的操作。IO Task Runner并不会阻塞Flutter。虽然在通过IO Task Runner加载图片和资源的时候可能会有延迟,但是还是建议为IO Task Runner单独建立一个线程。

创建单独的isolate

在UI Task Runner过载的情况下,可以创建单独的isolate。单独创建的isolate之间没有共享内存,所以它们之间的唯一通信方式只能通过Port进行,而且Dart中的消息传递总是异步的。isolate与线程有着本质的区别,操作系统内的线程之间是可以共享内存的,而isolate不会,这是最为关键的区别。那么如何创建isolate呢,代码如下:

 _isolateDemo() async{ //isolate所需要的参数必须要有SendPort,SendPort有必须要要有ReceivePort来创建 final receivePort=new ReceivePort(); //创建新的isolate,这里使用Isolatel.spawn函数创建 await Isolate.spawn(_isolate, receivePort.sendPort); //发送消息 var sendPort=await receivePort.first; var msg=await sendReceive(sendPort, "fpp"); print("received $msg"); msg=await sendReceive(sendPort, "bar"); print("received $msg");
  }
//新isolate的入口函数
_isolate(SendPort replyTo) async{
  //实例话一个receivePort用于接收消息
  var port=new ReceivePort();
  //把它的sendPort发送给宿主isolate,以便宿主可以给它发消息
  replyTo.send(port.sendPort);
  //监听消息,从Port里获取
  await for(var msg in port){ var data=msg[0]; SendPort replyTo=msg[1]; replyTo.send("接受到了:"+data); if(data=="bar")port.close();
  }
}
//对某一个Port发送消息,并接受结果
Future sendReceive(SendPort port,msg){
  ReceivePort response=new ReceivePort();
  port.send([msg,response.sendPort]);
  return response.first;
}

  
 
  • 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

如果你需要处理过度耗时的任务就可以使用如上方式进行创建isolate。

Stream事件流

Stream是与Flutter相关的一个重要概念,它首先是基于事件流来驱动并设计代码的,然后监听和订阅相关事件,并且对事件的变化进行处理和响应。Stream不是Flutter中特有的,而是Dart语言中自带的。我们可以把Stream想象成管道(pipe)的两端,它只允许从一端插入数据并通过管道从另外一端流出数据。我们可以通过StreamController来控制Stream(事件源),比如StreamController提供了类型为StreamSink,属性为Sink的控制器作为入口。

Stream可以传输什么?它支持任何类型数据的传输,包括基本值,事件,对象,集合等,即任何可能改变的数据都可以被Stream传毒和触发。当我们在传输数据时,可以通过listen函数监听来自StreamController的属性,在监听之后,可以通过StreamSubscription订阅对象并接受Stream发送数据变更的通知。

Stream也是异步处理的核心API,那么,它与同为异步处理的Future有何区别呢?答案时Future表示“将来”一次异步获取得到的数据,而Stream是多次异步获取得到的数据;Future将返回一个值,而Stream将返回多次值。在讲Future时我们提到了FutureBuilder类,对于Stream,它有StreamBuilder类负责监听Stream。当Stream数据流出时会自动重新构建组件,并通过Builder进行回调。

下面就是Stream的简单例子,代码如下:

class _MyHomePageState extends State<MyHomePage> { final StreamController<int> _streamController=StreamController<int>();
  int _counter=0; @override
  void dispose() { _streamController.close(); super.dispose();
  } @override
  Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text("数字的变化:"), StreamBuilder<int>( stream: _streamController.stream, initialData: 0, builder: (BuildContext context,AsyncSnapshot<int> snapshot){ return Text( '${snapshot.data}', style: Theme.of(context).textTheme.display1, ); }, ), Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: (){ _streamController.sink.add(++_counter); }, tooltip: '自加', child: Icon(Icons.add), ), );
  }
}

  
 
  • 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

上面就是要给简单的计数程序,不过这里是通过stream实现的,上面没什么难点,都是常用到的一些代码,这里就不做过多的赘述了。

文章来源: liyuanjinglyj.blog.csdn.net,作者:李元静,版权归原作者所有,如需转载,请联系作者。

原文链接:liyuanjinglyj.blog.csdn.net/article/details/104502642

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。