flutter-异步编程

首先看一张Flutter体系结构图:

1.png

我们只关注线程相关信息

1、Framework:我们直接接触的层级

2、engine:Dart Isolate Setup,创建Isolate,类似于DartVM中的线程,他的架构就是一个循环:event loops

但这一层并不创建及管理线程,它要求Embeder提供四个Task Runner,也就是和真正的线程类似的玩意,并不是真正的线程。

因为尽管Flutter Engine不在乎Runner具体跑在哪个线程,但是它需要线程配置在整一个生命周期里面保持稳定。也就是说一个Runner最好始终保持在同一线程运行

3、embedder:Thread Setup,真正的线程创建及管理者,Embeder指的是将引擎移植到平台的中间层代码。

一、task runners

flutter没有线程和进程的概念,而是一个底层的任务运行器,是把各个task runners运行在不同线程的(也可以相同,不推荐),我们要牢记一点:embedder是真正的线程创建及管理者

分为四种:

2.png

1、Platform Task Runner

对应线程:
Android:主线程
iOS:主线程

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

主要功能:

1)跟Flutter Engine的所有交互(接口调用)必须发生在Platform Thread。

2)处理平台的消息

2、UI Task Runner

虽然名字叫UI,但是并不是Android,iOS的UI线程,只是flutter的UI线程,也就是我们平时开发的线程。

1)用于执行Dart root isolate代码,Root isolate运行应用的main code。

2)渲染逻辑,告诉Engine最终的渲染

对于每一帧,Engine要做的事情有:

  • Root isolate通知Flutter Engine有帧需要渲染。

  • Flutter Engine通知平台,需要在下一个vsync的时候得到通知。

  • 平台等待下一个vsync(Android在需要绘制的时候系统发送的一个信号,16ms发送一次,代表一帧,具体请查阅资料)

  • 对创建的对象和Widgets进行Layout并生成一个Layer Tree,这个Tree马上被提交给Flutter Engine。当前阶段没有进行任何光栅化,这个步骤仅是生成了对需要绘制内容的描述。

  • 创建或者更新Tree,这个Tree包含了用于屏幕上显示Widgets的语义信息。这个东西主要用于平台相关的辅助Accessibility元素的配置和渲染。

3)处理来自Native Plugins的消息

4)timers

5)microtasks

6)异步 I/O 操作(sockets, file handles, 等)

3、GPU Task Runner

GPU执行的线程

UI Task Runner创建的Layer Tree信息是平台不相关,具体如何实现绘制取决于具体绘制平台和方式,可以是OpenGL,Vulkan等(注意两个平台所指不同。

GPU Runner可以导致UI Runner的帧调度的延迟,GPU Runner的过载会导致Flutter应用的卡顿

4、IO Task Runner

主要功能是从图片存储(比如磁盘)中读取压缩的图片格式,将图片数据进行处理为GPU Runner的渲染做好准备

iOS和Android:flutter为每个引擎实例的UI,GPU和IO任务运行程序创建专用线程。所有引擎实例共享相同的Platform Thread和Platform Task Runner。

二、Isolate

1、isolate是什么

首先我们需要知道:Dart是一个单线程语言

而isolate是Dart并发模式的实现。与一般意义上的线程不同,isolate翻译过来是’隔离‘的意思,所以他是顺序执行并且独享内存的,所以多个isolate之间不能共享内存,而正因为Dart没有共享内存的并发,没有竞争的可能性所以不需要锁,也就不用担心死锁的问题。

Dart本身抽象了isolate和thread,实际上底层还是使用操作系统的提供的OSThread。

3.png

而一个isolate就是由event loops方式来实现的。

2、event loops

关于event loops和isolate的官方推荐文档:Dart 异步编程:隔离区和事件循环

我们在写Dart代码的时候,就只有两种代码,

同步代码:就是一行行写下来的代码

异步代码:就是以Future等修饰的代码

并不是指的我们平常异步,这两种代码的区别只有一个:代码运行的顺序是不同的,先运行同步代码,再运行异步代码,顺序执行。

而异步代码是运行在event loop里的,event loops直译过来就是事件循环,对于Android开发者来说,再熟悉不过了,就是handler机制。

用两张图来表示:

4.png

和handler一样,不停的向event Queue里取事件执行。

5.png

这张图就更明显了,但是也看出了和handler不一样的地方:

首先会从microtask Queue中取事件,然后才向event Queue里取事件,也就是说microtask会优先执行。

我从网上找了一个例子:

import 'dart:async';
void isolateTest() {
  print('isolateTest #1 of 2');
  scheduleMicrotask(() => print('microtask #1 of 3'));
  //使用delay方式,是将此task放到queue的尾部,
  //若前面有耗时操作,不一定能准时执行
  new Future.delayed(new Duration(seconds:1),
      () => print('future #1 (delayed)'));
      
  //使用then,是表示在此task执行后立刻执行
  new Future(() => print('future #2 of 4'))
      .then((_) => print('future #2a'))
      .then((_) {
        print('future #2b');
        scheduleMicrotask(() => print('microtask #0 (from future #2b)'));
      })
      .then((_) => print('future #2c'));

  scheduleMicrotask(() => print('microtask #2 of 3'));

  new Future(() => print('future #3 of 4'))
      .then((_) => new Future(
                   () => print('future #3a (a new future)')))
      .then((_) => print('future #3b'));

  new Future(() => print('future #4 of 4'))
  .then((_){
    new Future(() => print('future #4a'));
  })
  .then((_) => print('future #4b'));
  scheduleMicrotask(() => print('microtask #3 of 3'));
  print('isolateTest #2 of 2');
}

关键位置已做好注释,具体逻辑配合event loops原理理解,这里直接放出log:

isolateTest #1 of 2
isolateTest #2 of 2
microtask #1 of 3
microtask #2 of 3
microtask #3 of 3
future #2 of 4
future #2a
future #2b
future #2c
microtask #0 (from future #2b)
future #3 of 4
future #4 of 4
future #4b
future #3a (a new future)
future #3b
future #4a
future #1 (delayed)

而我们刚学习Flutter时,如果要实现’异步‘,一般是使用await,async关键字实现,就像这样

void isolateTest() async {
    //Flutter的http框架Dio
    var future = await new Future.delayed(new Duration(seconds:1),
      () => print('future #1 (delayed)'));
    setState(() {
      _counter = future.data.hashCode;
    });
}

await 表达式之后也会生成一个future,而这样生成的有什么不一样呢

其实是一样的,只是执行顺序就会发生一些改变,我们把刚刚的代码稍微修改一下

void isolateTest() async {
  print('isolateTest #1 of 2');
  scheduleMicrotask(() => print('microtask #1 of 3'));
  //await修饰
  await new Future.delayed(new Duration(seconds: 3),
          () => print('future #1 (delayed)'));

//...省略
}

//在main中调用
void main() {
  isolateTest();
  print('main');
}


//结果
I/flutter (13815): isolateTest #1 of 2
I/flutter (13815): main
I/flutter (13815): microtask #1 of 3
I/flutter (13815): future #1 (delayed)
I/flutter (13815): isolateTest #2 of 2
//....省略

其实就是执行到await语句时将async方法内的所有剩余代码都放到event loops队列尾部,直到await执行完毕后继续执行该task。

3、创建自己的isolate

根据异步知识,我们可以写一个普通的网络请求Demo-APP:

class _MyHomePageState extends State<MyHomePage> {
  var _counter = 0;
  var who = "You have pushed the button this many times:";

void _incrementCounter() async {
  var future = await new Dio().get('http://www.baidu.com');
  setState(() {
    _counter = future.hashCode;
  });
}

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              who,
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            IconButton(
              icon: Icon(Icons.thumb_up),
              onPressed: () {
                setState(() {
                  if (who == "You have pushed the button this many times:") {
                    who = "I have pushed the button this many times:";
                  } else {
                    who = "You have pushed the button this many times:";
                  }
                });
              },
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

点击floatingActionButton会做一个网络请求的耗时操作,使用await关键字实现异步效果,可以在网络请求的同时,点击IconButton,UI会做相应的变化。

下面我们修改_incrementCounter方法,让他做一个计算的耗时操作

void _incrementCounter() async {
  var future = await new Future(() {
    var i = 0;
    while (true) {
      print("${i++}");
      if (i > 200000) {
        break;
      }
    }
    return new Dio().get('http://www.baidu.com');
  });
  setState(() {
    _counter = future.hashCode;
  });
}

在future里面做一个20万次打印的操作,这个时候我们再次点击floatingActionButton,然后点击IconButton,UI就会卡顿。

这里就要分IO耗时和CPU耗时了,网络请求和文件读取一类的操作不会使用CPU,而是纯等待IO而已,所以我们CPU可以去event Queue里取其他Task执行,而CPU耗时就是实实在在的CPU计算了。由于Dart是单线程的,Futrue的异步并不是真正的异步,所以会造成卡顿。

那我们如何真正新开一个线程呢?也就是需要新建一个isolate!

需要用到的API: Isolate.spawn,下面是一个网上找的例子:

import 'dart:async';
import 'dart:isolate';

main() async {
  var receivePort = new ReceivePort();
  //关键代码,新建一个isolate
  await Isolate.spawn(echo, receivePort.sendPort);

  // The 'echo' isolate sends it's SendPort as the first message
  var sendPort = await receivePort.first;

  var msg = await sendReceive(sendPort, "foo");
  print('received $msg');
  msg = await sendReceive(sendPort, "bar");
  print('received $msg');
}

// the entry point for the isolate
// 异步代码
echo(SendPort sendPort) async {
  // Open the ReceivePort for incoming messages.
  var port = new ReceivePort();

  // Notify any other isolates what port this isolate listens to.
  sendPort.send(port.sendPort);

  await for (var msg in port) {
    var data = msg[0];
    SendPort replyTo = msg[1];
    replyTo.send(data);
    if (data == "bar") port.close();
  }
}

/// sends a message on a port, receives the response,
/// and returns the message
Future sendReceive(SendPort port, msg) {
  ReceivePort response = new ReceivePort();
  port.send([msg, response.sendPort]);
  return response.first;
}

====================================
$ isolates.dart
received foo
received bar

过程过于复杂,我就简单描述一下大致流程:

就是通过Isolate.spawn新建线程,该函数的第一个参数是一个函数,该函数由我们自己实现且运行在新线程中,并通过ReceivePort来进行线程间通信。

看着这个Dart新建线程的方式我的第一反应是蛋疼的,这也太麻烦了八!!

没关系,Flutter体贴的为我们准备了封装好的API:compute

void main() async{
  //调用compute函数,传入耗时函数
  print( await compute(LongTimeTask, 20));
  runApp(MyApp());
}
//参数是上面传入20
int LongTimeTask(int n){
  return //一个耗时操作;
}

compute使用超级简单,但也有缺陷,就是不能多次进行线程间通讯,但是很多时候我们并不需要多次通讯,只需要耗时操作后的值就OK,所以还是很实用的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值