Flutter 中的异步编程总结

Flutter 中的异步编程总结html

1、 Dart 中的事件循环模型

Dart 是一种单线程模型运行语言,其运行原理以下图所示:编程

Dart 在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是“微任务队列” microtask queue,另外一个叫作“事件队列” event queue。 从图中能够发现,微任务队列的执行优先级高于事件队列。bash

如今咱们来介绍一下Dart线程运行过程,如上图中所示,入口函数 main() 执行完后,消息循环机制便启动了。 首先会按照先进先出的顺序逐个执行微任务队列中的任务,当全部微任务队列执行完后便开始执行事件队列中的任务,事件任务执行完毕后再去执行微任务, 如此循环往复,生生不息。微信

在Dart中,全部的外部事件任务都在事件队列中,如IO、计时器、点击、以及绘制事件等,而微任务一般来源于Dart内部,而且微任务很是少, 之因此如此,是由于微任务队列优先级高,若是微任务太多,执行时间总和就越久,事件队列任务的延迟也就越久, 对于GUI应用来讲最直观的表现就是比较卡,因此必须得保证微任务队列不会太长。网络

在事件循环中,当某个任务发生异常并无被捕获时,程序并不会退出,而直接致使的结果是当前任务的后续代码就不会被执行了, 也就是说一个任务中的异常是不会影响其它任务执行的。并发

咱们能够看出,将任务加入到MicroTask中能够被尽快执行,但也须要注意,当事件循环在处理MicroTask队列时,Event队列会被卡住,应用程序没法处理鼠标单击、I/O消息等等事件。 同时,当事件循坏出现异常时,dart 中也能够经过 try/catch/finally 来捕获异常。app

2、任务调度

基于上述理论,看一下任务调度less

2.1 添加到 MicroTask 任务队列 两种方法:

import  'dart:async';
void  myTask(){
  print("this is my task");
}
void  main() {
  /// 1. 使用 scheduleMicrotask 方法添加
  scheduleMicrotask(myTask);
  /// 2. 使用Future对象添加
  new  Future.microtask(myTask);
}
复制代码

两个 task 都会被执行,控制台输出:异步

this is my task
this is my task
复制代码

2.2 添加到 Event 队列。

import  'dart:async';

void  myTask(){
  print("this is my event task");
}

void  main() {
  new  Future(myTask);
}
复制代码

控制台输出:async

this is my event task
复制代码

示例:

import  'dart:async';

void  main() {
  print("main start");

  new  Future((){
    print("this is my task");
  });

  new  Future.microtask((){
    print("this is microtask");
  });

  print("main stop");
}

复制代码

控制台输出:

main start
main stop
this is microtask
this is my task
复制代码

能够看到,代码的运行顺序并非按照咱们的编写顺序来的,将任务添加到队列并不等于马上执行,它们是异步执行的,当前main方法中的代码执行完以后,才会去执行队列中的任务,且MicroTask队列运行在Event队列以前。

2.三、延时任务

new  Future.delayed(new  Duration(seconds:1),(){
    print('task delayed');
});

复制代码

使用 Future.delayed 可使用延时任务,可是延时任务不必定准

import  'dart:async';
import  'dart:io';

void  main() {
  var now = DateTime.now();

  print("程序运行开始时间:" + now.toString());

  new Future.delayed(new  Duration(seconds:1),(){
    now = DateTime.now();
    print('延时任务执行时间:' + now.toString());
  });

  new Future((){
    // 模拟耗时5秒
    sleep(Duration(seconds:5));
    now = DateTime.now();
    print("5s 延时任务执行时间:" + now.toString());
  });

  now = DateTime.now();
  print("程序结束执行时间:" + now.toString());
}

复制代码

输出结果:

程序运行开始时间:2019-09-11 20:46:18.321738
程序结束执行时间:2019-09-11 20:46:18.329178
5s 延时任务执行时间:2019-09-11 20:46:23.330951
延时任务执行时间:2019-09-11 20:46:23.345323
复制代码

能够看到,5s 的延时任务,确实实在当前程序运行以后的 5s 后获得了执行,可是另外一个延时任务,是在当前延时的基础上又延时执行的, 也就是,延时任务必须等前面的耗时任务执行完,才获得执行,这将致使延时任务延时并不许。

3、Future 与 FutureBuilder

Dart类库有很是多的返回Future或者Stream对象的函数。 这些函数被称为异步函数:它们只会在设置好一些耗时操做以后返回,好比像 IO操做。而不是等到这个操做完成。

Future与JavaScript中的Promise很是类似,表示一个异步操做的最终完成(或失败)及其结果值的表示。简单来讲,它就是用于处理异步操做的,异步处理成功了就执行成功的操做,异步处理失败了就捕获错误或者中止后续操做。一个Future只会对应一个结果,要么成功,要么失败。

因为自己功能较多,这里咱们只介绍其经常使用的API及特性。还有,请记住,Future 的全部API的返回值仍然是一个Future对象,因此能够很方便的进行链式调用。

3.1 Future 使用

建立方法: Future的几种建立方法

Future() Future.microtask() Future.sync() Future.value() Future.delayed() Future.error()

Future 和 Future.microtask 和 Future.delayed 上面已经演示过了。 Future.sync() 表示同步方法,会被当即执行: 如:

import  'dart:async';

void  main() {
  print("main start");

  new  Future.sync((){
    print("sync task");
  });

  new  Future((){
    print("async task");
  });

  print("main stop");
}

复制代码

输出:

main start
sync task
main stop
async task

复制代码

3.2 Future 中的回调

当Future中的任务完成后,咱们每每须要一个回调,这个回调当即执行,不会被添加到事件队列。 如:

import 'dart:async';

void main() {
  print("main start");


  Future fut =new Future.value(18);
  // 使用then注册回调
  fut.then((res){
    print(res);
  });

  // 链式调用,能够跟多个then,注册多个回调
  new Future((){
    print("async task");
  }).then((res){
    print("async task complete");
  }).then((res){
    print("async task after");
  });

  print("main stop");
}

复制代码

输出结果:

main start
main stop
18
async task
async task complete
async task after
复制代码

使用 catchError 捕获异常:

import 'dart:async';

void main() {
  print("main start");

  Future.delayed(new Duration(seconds: 2),(){
    //return "hi world!";
    throw AssertionError("Error");
  }).then((data){
    //执行成功会走到这里  
    print("success");
  }).catchError((e){
    //执行失败会走到这里
    print(e);
  });

  print("main stop");
}

复制代码

输出:

main start
main stop
Assertion failed
复制代码

在本示例中,咱们在异步任务中抛出了一个异常,then的回调函数将不会被执行,取而代之的是 catchError回调函数将被调用;可是,并非只有 catchError回调才能捕获错误,then方法还有一个可选参数onError,咱们也能够它来捕获异常:

import 'dart:async';

void main() {
  print("main start2");

  Future.delayed(new Duration(seconds: 2), () {
    //return "hi world!";
    throw AssertionError("Error");
  }).then((data) {
    print("success");
  }, onError: (e) {
    print(e);
  });

  print("main stop2");
}

复制代码

结果:

main start2
main stop2
Assertion failed
复制代码

whenComplete 必定获得执行:

import 'dart:async';

void main() {
  print("main start3");
  Future.delayed(new Duration(seconds: 2),(){
    //return "hi world!";
    throw AssertionError("Error");
  }).then((data){
    //执行成功会走到这里
    print(data);
  }).catchError((e){
    //执行失败会走到这里
    print(e);
  }).whenComplete((){
    //不管成功或失败都会走到这里
    print("this is the end...");
  });
  print("main stop3");
}

复制代码

结果:

main start3
main stop3
Assertion failed
this is the end...
复制代码

Future.wait 表示多个任务都完成以后的回调。

import 'dart:async';

void main() {
  print("main start4");

  Future.wait([
    // 2秒后返回结果
    Future.delayed(new Duration(seconds: 2), () {
      return "hello";
    }),
    // 4秒后返回结果
    Future.delayed(new Duration(seconds: 4), () {
      return " world";
    })
  ]).then((results){
    print(results[0]+results[1]);
  }).catchError((e){
    print(e);
  });

  print("main stop4");
}

复制代码

结果:

main start4
main stop4
hello world
复制代码

3.3 FutureBuilder 的使用

不少时候咱们会依赖一些异步数据来动态更新UI,好比在打开一个页面时咱们须要先从互联网上获取数据,在获取数据的过程当中咱们显式一个加载框,等获取到数据时咱们再渲染页面;又好比咱们想展现Stream(好比文件流、互联网数据接收流)的进度。固然,经过StatefulWidget咱们彻底能够实现上述这些功能。 但因为在实际开发中依赖异步数据更新UI的这种场景很是常见,所以Flutter专门提供了FutureBuilder和StreamBuilder两个组件来快速实现这种功能。

FutureBuilder会依赖一个Future,它会根据所依赖的Future的状态来动态构建自身。咱们看一下FutureBuilder构造函数:

FutureBuilder({
  this.future,
  this.initialData,
  @required this.builder,
})
复制代码
  • future:FutureBuilder依赖的Future,一般是一个异步耗时任务。

  • initialData:初始数据,用户设置默认数据。

  • builder:Widget构建器;该构建器会在Future执行的不一样阶段被屡次调用,构建器签名以下:

Function (BuildContext context, AsyncSnapshot snapshot)
复制代码

snapshot会包含当前异步任务的状态信息及结果信息 ,好比咱们能够经过snapshot.connectionState获取异步任务的状态信息、经过snapshot.hasError判断异步任务是否有错误等等,完整的定义读者能够查看AsyncSnapshot类定义。

示例:

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  Future<String> mockNetworkData() async {
    return Future.delayed(Duration(seconds: 2), () => "这是网络请求的数据。。。");
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: FutureBuilder<String>(
          future: mockNetworkData(),
          builder: (BuildContext context, AsyncSnapshot snapshot) {
            // 请求已结束
            if (snapshot.connectionState == ConnectionState.done) {
              if (snapshot.hasError) {
                // 请求失败,显示错误
                return Text("Error: ${snapshot.error}");
              } else {
                // 请求成功,显示数据
                return Text("Contents: ${snapshot.data}");
              }
            } else {
              // 请求未结束,显示loading
              return CircularProgressIndicator();
            }
          },
        ),
      ),
    // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

复制代码

效果:

4、async/await

在Dart1.9中加入了async和await关键字,有了这两个关键字,咱们能够更简洁的编写异步代码,而不须要调用Future相关的API 将 async 关键字做为方法声明的后缀时,具备以下意义

  • 被修饰的方法会将一个 Future 对象做为返回值
  • 该方法会同步执行其中的方法的代码直到第一个 await 关键字,而后它暂停该方法其余部分的执行;
  • 一旦由 await 关键字引用的 Future 任务执行完成,await的下一行代码将当即执行。
// 导入io库,调用sleep函数
import 'dart:io';

// 模拟耗时操做,调用sleep函数睡眠2秒
doTask() async{
  await sleep(const Duration(seconds:2));
  return "Ok";
}

// 定义一个函数用于包装
test() async {
  var r = await doTask();
  print(r);
}

void main(){
  print("main start");
  test();
  print("main end");
}
复制代码

结果:

main start
main end
Ok
复制代码

5、Stream 与 StreamBuilder

Stream 也是用于接收异步事件数据,和Future 不一样的是,它能够接收多个异步操做的结果(成功或失败)。 也就是说,在执行异步任务时,能够经过屡次触发成功或失败事件来传递结果数据或错误异常。 Stream 经常使用于会屡次读取数据的异步任务场景,如网络内容下载、文件读写等。

5.1 Stream 的使用

void main(){
  print("main start");
  Stream.fromFutures([
    // 1秒后返回结果
    Future.delayed(new Duration(seconds: 1), () {
      return "hello 1";
    }),
    // 抛出一个异常
    Future.delayed(new Duration(seconds: 2),(){
      throw AssertionError("Error");
    }),
    // 3秒后返回结果
    Future.delayed(new Duration(seconds: 3), () {
      return "hello 3";
    })
  ]).listen((data){
    print(data);
  }, onError: (e){
    print(e.message);
  },onDone: (){

  });
  print("main end");
}

复制代码

结果:

main start
main end
hello 1
Error
hello 3
复制代码

5.1 StreamBuilder 的使用

咱们知道,在Dart中Stream 也是用于接收异步事件数据,和Future 不一样的是,它能够接收多个异步操做的结果,它经常使用于会屡次读取数据的异步任务场景,如网络内容下载、文件读写等。StreamBuilder正是用于配合Stream来展现流上事件(数据)变化的UI组件。 下面看一下StreamBuilder的默认构造函数:

StreamBuilder({
  Key key,
  this.initialData,
  Stream<T> stream,
  @required this.builder,
})
复制代码

能够看到和FutureBuilder的构造函数只有一点不一样:前者须要一个future,然后者须要一个stream。

示例:

Stream<int> counter() {
    return Stream.periodic(Duration(seconds: 1), (i) {
      return i;
    });
  }
复制代码
Widget buildStream (BuildContext context) {
      return StreamBuilder<int>(
        stream: counter(), //
        //initialData: ,// a Stream<int> or null
        builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
          if (snapshot.hasError)
            return Text('Error: ${snapshot.error}');
          switch (snapshot.connectionState) {
            case ConnectionState.none:
              return Text('没有Stream');
            case ConnectionState.waiting:
              return Text('等待数据...');
            case ConnectionState.active:
              return Text('active: ${snapshot.data}');
            case ConnectionState.done:
              return Text('Stream已关闭');
          }
          return null; // unreachable
        },
      );
    }

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body:Center(
        child:  buildStream(context),
      )
    // This trailing comma makes auto-formatting nicer for build methods.
    );
复制代码

效果:

6、isolate

Dart是基于单线程模型的语言。可是在开发当中咱们常常会进行耗时操做好比网络请求,这种耗时操做会堵塞咱们的代码,因此在Dart也有并发机制,名叫isolate。 APP的启动入口main函数就是一个相似Android主线程的一个主isolate。和Java的Thread不一样的是,Dart中的isolate没法共享内存,相似于Android中的多进程。

6.1 使用 spawnUri 建立 isolate

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 = new ReceivePort();
  ///建立发送端
  SendPort port1 = rp.sendPort;
  ///建立新的 isolate ,并传递了发送端口。
  Isolate newIsolate = await Isolate.spawnUri(new 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 = new 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 能够理解为一根水管,消息的传递方向时固定的,把这个水管的发送端发送给对方, 对方就能经过这个水管发送消息。

6.2 经过 spawn 建立 isolate

除了使用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 发送的]
复制代码

参考:

juejin.im/post/5cdbf2… juejin.im/post/5c876e… book.flutterchina.club/chapter2/th… book.flutterchina.club/chapter7/fu… book.flutterchina.club/chapter1/da…

最后

欢迎关注「Flutter 编程开发」微信公众号 。