Flutter async、await、Future

一、Flutter中的Event Loop

Flutter是有Dart语言开发的,与Android一样是事件驱动的,在Android中的结构是Looper/Handler,相信熟悉Android开发的的同事对Looper循环器和Handler都非常深刻。但在Dart语言中也有自己的Event Loop,那Dart中的Event Loop是什么样的结构呢? 下面我们先通过一个简单的流程图来观察和了解一下Dart中的Event Loop。

                     

   Dart处理事件的的流程,从流程图中我们可以总结出:

  •  首先处理MicroTask queue中的所有MicroTask任务
  •  处理完所有的MicroTask后执行一条Event又回到了MicroTask
  •  继续循环执行MicroTask,知道所有的event处理完。

我们需要注意的是只有当MicroTask都消费完之后才开始执行Event事件,并且执行一条Event之后又循环执行MicroTask。依次反复执行,知道所有的Event都执行完毕。了解了Dart中的Event Loop,那接下来我们看一下Dart中的异步任务。

二、Flutter/Dart的异步UI

Dart是单线程执行模型,支持Isolates(在另一个线程上运行Dart代码的方式)、事件循环和异步编程。 除非您启动一个Isolate,否则您的Dart代码将在主UI线程中运行,并由事件循环驱动

比如我们可以在UI线程上运行网络请求代码而不会导致UI挂起(译者语:因为网络请求是异步的):

 loadData() async {
    String dataURL = "https://jsonplaceholder.typicode.com/posts";
    http.Response response = await http.get(dataURL);
    setState(() {
      widgets = json.decode(response.body);
    });

要更新UI,您可以调用setState,这会触发build方法再次运行并更新数据。

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    loadData();
  }

在这里我们需要注意async 和await的用法

1、Async和await

asyncawait是什么?它们是Dart语言的关键字,有了这两个关键字,可以让你用同步代码的形式写出异步代码

我们首先看一个案例:

首先我们用async修饰一个方法,来处理发杂的数学计算。代码如下:

syncFibonacci(int n) async {
  return n < 2 ? n: subtraction(n-1) ;
}
int subtraction(int num){
    return (num-2) + (num-1);
}

然后我们在创建一个fun方法,调用syncFibonacci方法如下:

void fun(){
    int num = await syncFibonacci(20);
    print('$num')
}

需要注意的时我们调用的时候,必须添加await方法,才能顺利执行出最终结果,如果我们不用await修饰的时候,就会包如下错误:

Unhandled Exception: type 'Future<dynamic>' is not a subtype of type 'int'

Future类型不匹配的错误,

那为什么会报Future类型不匹配的错误?因为num是int类型,而函数syncFibonacci(20)是一个异步操作函数,其返回值是一个await延迟执行的结果。在Dart中,有await标记的运算,其结果值都是一个Future对象,Future不是int类型,所以就报错了。所以为了获取到延迟执行的结果,Dart规定有async标记的函数,只能由await来调用。这样我们就可以很好的处理返回结果了。

 

接下来我们在看另外一个案例:

void main() async{
  print('main() run');
  teach();
  print('mian() end');
  ...
}
teach() async {
  print('  teach() run');
  String result = await food();
  print('teach() $result');
}
food() async {
  print("food() run");
  return "hello";
}

附上我们在控制台看到的结果:

              

从结果我们可以总结如下:

await并不是程序运行到这里等待Future完成。而是立刻结束当前函数的执行并返回一个Future。函数内剩余代码通过调度异步执行。

  • await只能在async函数中出现。
  • async函数中可以出现多个await,每遇见一个就返回一个Future, 实际结果类似于用then串起来的回调。
  • async函数也可以没有await, 在函数体同步执行完毕以后返回一个Future

2、Future

我们首先看一下Future的创建,Future的创建可以通过一下四种方式创建

  //通过new 直接创建Future
  Future((){print('Future 创建');});
  //创建一个Future,在duration后执行
  Future.delayed(const Duration(seconds:1), (){print('在1秒后Future');});
  //创建一个microtask中运行的future
  Future.microtask((){print('在microtask中运行Future');});
  //创建一个同步执行的future
  Future.sync((){print('同步运行的Future');});

注意:Future的创建是在UI线程中执行的

有了Future的对象之后,我么可以通过then串将传递过来的回调函数调度到任务队列异步执行。

Future((){print('创建了Future对象');})
      .then((_){print('执行函数一');})
        ...
      .then((_){print('执行函数二');})
      //用来捕获异常,类似与Java中的try{}catch (Exception e){ System.out.println(e.getMessage()); }
      .catchError((error)=>print('$error'))
      //无论有没有异常都会执行的whenComplete(),类似于try..catch..中的finally{}操作。
      .whenComplete(()=> print('执行完成'));

通过Future对象之后,我么执行then函数,可以是一个then函数,也可以是多个then函数,同时我们在请求网络或文件读写等负责逻辑的时候,我们可以通过catchErro来捕获异常,同时在whenComplete来关闭文件流,数据库的游标对象等操作。

 需要注意的是:

  • 你通过then串起来的那些回调函数在Future创建完成的时候会被立即执 行,也就是说它们是同步执行,而不是被调度异步执行。
  •  如果Future在调用then串起回调函数之前已经完成,那么这些回调函数会被调度到microtask queue异步执行。
  • 通过Future()Future.delayed()实例化的Future不会同步执行,它们会被调度到事件队列异步执行。
  • 通过Future.value()实例化的Future会被调度到microtask queue异步完成,类似于第2条。
  • 通过Future.sync()实例化的Future会同步执行其入参函数,然后(除非这个入参函数返回一个Future)调度到microtask queue来完成自己,类似于第2条。

3、使用Isolate

在Flutter中,可以利用多个CPU内核来执行耗时或计算密集型任务。这是通过使用Isolates来完成的。是一个独立的执行线程,它运行时不会与主线程共享任何内存。这意味着你不能从该线程访问变量或通过调用setState来更新你的UI。那么Isolate是如何来完成数据交互的呢?我们以计算斐波那契数列为例

首先分装一个函数,用来计算斐波那契数列值

int syncFibonacci(int n) {
  return n < 2 ? n : syncFibonacci(n - 2) + syncFibonacci(n - 1);
}

然后我们采用Isolate来计算n的值

*方法一,计算斐波那契数里*/
void fun(int n) async {
   /*
    * 首先创建一个ReceivePort,为什么要创建这个?
    * 因为创建Isolate所需的参数,必须要有SendPort,SendPort需要ReceivePort来创建
    * */
  ReceivePort response = new ReceivePort();
   /*
    * 开始创建Isolate,通过Isolate调用spawn函数,将我们创建的_isolate传递过去。
    * _isolate是创建Isolate必须要的参数
    * */
  await Isolate.spawn(_isolate, response.sendPort);

  //获取sendPort来发送参数
  SendPort sendPort = await response.first;
  //创建接收消息的ReceivePort
  ReceivePort receivePort = new ReceivePort();
  //发送数据
  sendPort.send([n, receivePort.sendPort]);
  /*接收Isolate处理完成程序返回的数据*/
  int num = await receivePort.first;
  print("============================$num");
}

//创建isolate必须要的参数
void _isolate(SendPort initReplyto) {
  final ReceivePort port = new ReceivePort();
  //绑定
  initReplyto.send(port.sendPort);
  //监听
  port.listen((messsage) {
    //获取数据并解析
    final int data = messsage[0];
    final SendPort send = messsage[1];
    //返回结果
    send.send(syncFibonacci(data));
  });
}

需要注意的是是在UI和Isolate里面使用ReceivePort进行双向通信,那么既然是双向绑定,我们就可以不断调用或者多次监听返回。比如通过长连接获取服务器端的数据,当服务器数据变化的时候,能够及时更新UI中的变化。

4、 使用compute

对上述案例,如果使用compute处理的话,就变得比较简单,

/*方法一,计算斐波那契数里*/
fun() async {
  int num = await compute(syncFibonacci, 20);
  print('===============$num');
}

通过对比,我们就发现compute的使用比较简单,因为在dart中的Isolate比较重量级,UI线程和Isolate中的数据的传输比较复杂,因此flutter为了简化用户代码,在foundation库中封装了一个轻量级compute操作,

但要使用compute,必须注意的有两点,

a、我们的compute中运行的函数,必须是顶级函数或者是static函数,

b、compute传参,只能传递一个参数,一次调用返回值也只有一个:

其次compute的使用还是有些限制,它没有办法多次返回结果,也没有办法持续性的传值计算,每次调用,相当于新建一个隔离,如果调用过多的话反而会适得其反。在有些些业务中我们可以使用compute,但是有些些业务中,我们只能使用dart提供的Isolate了,比如处理不断监听服务端返回的消息信息。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章