Flutter是有Dart语言开发的,与Android一样是事件驱动的,在Android中的结构是Looper/Handler,相信熟悉Android开发的的同事对Looper循环器和Handler都非常深刻。但在Dart语言中也有自己的Event Loop,那Dart中的Event Loop是什么样的结构呢? 下面我们先通过一个简单的流程图来观察和了解一下Dart中的Event Loop。
Dart处理事件的的流程,从流程图中我们可以总结出:
我们需要注意的是只有当MicroTask都消费完之后才开始执行Event事件,并且执行一条Event之后又循环执行MicroTask。依次反复执行,知道所有的Event都执行完毕。了解了Dart中的Event Loop,那接下来我们看一下Dart中的异步任务。
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的用法
async
和await
是什么?它们是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' 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
。我们首先看一下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来关闭文件流,数据库的游标对象等操作。
需要注意的是:
Future创建
完成的时候会被立即执 行,也就是说它们是同步执行,而不是被调度异步执行。Future
在调用then
串起回调函数之前已经完成,那么这些回调函数会被调度到microtask queue异步执行。Future()
和Future.delayed()
实例化的Future
不会同步执行,它们会被调度到事件队列异步执行。Future.value()
实例化的Future
会被调度到microtask queue异步完成,类似于第2条。Future.sync()
实例化的Future
会同步执行其入参函数,然后(除非这个入参函数返回一个Future
)调度到microtask queue来完成自己,类似于第2条。在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中的变化。
对上述案例,如果使用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了,比如处理不断监听服务端返回的消息信息。