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

前言

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

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

isolate

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

全部的Dart代码都是在isolate上运行的。一般状况下,咱们的应用都是运做在main isolate中的,在必要时咱们能够经过相关的API建立新的isolate,以便更好的利用CPU的特性,并以此提升效率。须要注意一点,多个isolate没法共享内存,必须经过相关的API通讯才能够。bash

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中。app

(4)microtask queue和event queue都为空,则App能够正常退出。异步

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

咱们直接来看一个例子,代码以下:ide

_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("结束");

这里,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;
}

若是你须要处理过分耗时的任务就可使用如上方式进行建立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),
      ),
    );
  }
}

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