<简书 — 刘小壮> https://www.jianshu.com/p/54da18ed1a9egit
Flutter
默认是单线程任务处理的,若是不开启新的线程,任务默认在主线程中处理。github
和iOS应用很像,在Dart
的线程中也存在事件循环和消息队列的概念,但在Dart
中线程叫作isolate
。应用程序启动后,开始执行main
函数并运行main isolate
。编程
每一个isolate
包含一个事件循环以及两个事件队列,event loop
事件循环,以及event queue
和microtask queue
事件队列,event
和microtask
队列有点相似iOS的source0
和source1
。json
isolate
消息等外部事件。isolate
内部添加事件,事件的优先级比event queue
高。这两个队列也是有优先级的,当isolate
开始执行后,会先处理microtask
的事件,当microtask
队列中没有事件后,才会处理event
队列中的事件,并按照这个顺序反复执行。但须要注意的是,当执行microtask
事件时,会阻塞event
队列的事件执行,这样就会致使渲染、手势响应等event
事件响应延时。为了保证渲染和手势响应,应该尽可能将耗时操做放在event
队列中。安全
在异步调用中有三个关键词,async
、await
、Future
,其中async
和await
须要一块儿使用。在Dart
中能够经过async
和await
进行异步操做,async
表示开启一个异步操做,也能够返回一个Future
结果。若是没有返回值,则默认返回一个返回值为null
的Future
。网络
async
、await
本质上就是Dart
对异步操做的一个语法糖,能够减小异步调用的嵌套调用,而且由async
修饰后返回一个Future
,外界能够以链式调用的方式调用。这个语法是JS
的ES7
标准中推出的,Dart
的设计和JS
相同。架构
下面封装了一个网络请求的异步操做,而且将请求后的Response
类型的Future
返回给外界,外界能够经过await
调用这个请求,并获取返回数据。从代码中能够看到,即使直接返回一个字符串,Dart
也会对其进行包装并成为一个Future
。并发
Future<Response> dataReqeust() async {
String requestURL = 'https://jsonplaceholder.typicode.com/posts';
Client client = Client();
Future<Response> response = client.get(requestURL);
return response;
}
Future<String> loadData() async {
Response response = await dataReqeust();
return response.body;
}
复制代码
在代码示例中,执行到loadData
方法时,会同步进入方法内部进行执行,当执行到await
时就会中止async
内部的执行,从而继续执行外面的代码。当await
有返回后,会继续从await
的位置继续执行。因此await
的操做,不会影响后面代码的执行。框架
下面是一个代码示例,经过async
开启一个异步操做,经过await
等待请求或其余操做的执行,并接收返回值。当数据发生改变时,调用setState
方法并更新数据源,Flutter
会更新对应的Widget
节点视图。异步
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
复制代码
Future
就是延时操做的一个封装,能够将异步任务封装为Future
对象。获取到Future
对象后,最简单的方法就是用await
修饰,并等待返回结果继续向下执行。正如上面async、await
中讲到的,使用await
修饰时须要配合async
一块儿使用。
在Dart
中,和时间相关的操做基本都和Future
有关,例如延时操做、异步操做等。下面是一个很简单的延时操做,经过Future
的delayed
方法实现。
loadData() {
// DateTime.now(),获取当前时间
DateTime now = DateTime.now();
print('request begin $now');
Future.delayed(Duration(seconds: 1), (){
now = DateTime.now();
print('request response $now');
});
}
复制代码
Dart
还支持对Future
的链式调用,经过追加一个或多个then
方法来实现,这个特性很是实用。例如一个延时操做完成后,会调用then
方法,而且能够传递一个参数给then
。调用方式是链式调用,也就表明能够进行不少层的处理。这有点相似于iOS的RAC
框架,链式调用进行信号处理。
Future.delayed(Duration(seconds: 1), (){
int age = 18;
return age;
}).then((onValue){
onValue++;
print('age $onValue');
});
复制代码
若是想要了解async
、await
的原理,就要先了解协程的概念,async
、await
本质上就是协程的一种语法糖。协程,也叫做coroutine
,是一种比线程更小的单元。若是从单元大小来讲,基本能够理解为进程->线程->协程。
在弄懂协程以前,首先要明白并发和并行的概念,并发指的是由系统来管理多个IO的切换,并交由CPU去处理。并行指的是多核CPU在同一时间里执行多个任务。
并发的实现由非阻塞操做+事件通知来完成,事件通知也叫作“中断”。操做过程分为两种,一种是CPU对IO进行操做,在操做完成后发起中断告诉IO操做完成。另外一种是IO发起中断,告诉CPU能够进行操做。
线程本质上也是依赖于中断来进行调度的,线程还有一种叫作“阻塞式中断”,就是在执行IO操做时将线程阻塞,等待执行完成后再继续执行。但线程的消耗是很大的,并不适合大量并发操做的处理,而经过单线程并发能够进行大量并发操做。当多核CPU出现后,单个线程就没法很好的利用多核CPU的优点了,因此又引入了线程池的概念,经过线程池来管理大量线程。
在程序执行过程当中,离开当前的调用位置有两种方式,继续调用其余函数和return
返回离开当前函数。可是执行return
时,当前函数在调用栈中的局部变量、形参等状态则会被销毁。
协程分为无线协程和有线协程,无线协程在离开当前调用位置时,会将当前变量放在堆区,当再次回到当前位置时,还会继续从堆区中获取到变量。因此,通常在执行当前函数时就会将变量直接分配到堆区,而async
、await
就属于无线协程的一种。有线协程则会将变量继续保存在栈区,在回到指针指向的离开位置时,会继续从栈中取出调用。
以async
、await
为例,协程在执行时,执行到async
则表示进入一个协程,会同步执行async
的代码块。async
的代码块本质上也至关于一个函数,而且有本身的上下文环境。当执行到await
时,则表示有任务须要等待,CPU则去调度执行其余IO,也就是后面的代码或其余协程代码。过一段时间CPU就会轮训一次,看某个协程是否任务已经处理完成,有返回结果能够被继续执行,若是能够被继续执行的话,则会沿着上次离开时指针指向的位置继续执行,也就是await
标志的位置。
因为并无开启新的线程,只是进行IO中断改变CPU调度,因此网络请求这样的异步操做可使用async
、await
,但若是是执行大量耗时同步操做的话,应该使用isolate
开辟新的线程去执行。
若是用协程和iOS的dispatch_async
进行对比,能够发现两者是比较类似的。从结构定义来看,协程须要将当前await
的代码块相关的变量进行存储,dispatch_async
也能够经过block
来实现临时变量的存储能力。
我以前还在想一个问题,苹果为何不引入协程的特性呢?后来想了一下,await
和dispatch_async
均可以简单理解为异步操做,OC的线程是基于Runloop
实现的,Dart
本质上也是有事件循环的,并且两者都有本身的事件队列,只是队列数量和分类不一样。
我以为当执行到await
时,保存当前的上下文,并将当前位置标记为待处理任务,用一个指针指向当前位置,并将待处理任务放入当前isolate
的队列中。在每一个事件循环时都去询问这个任务,若是须要进行处理,就恢复上下文进行任务处理。
这里想提一下JS
里的Promise
语法,在iOS中会出现不少if
判断或者其余的嵌套调用,而Promise
能够把以前横向的嵌套调用,改为纵向链式调用。若是能把Promise
引入到OC里,可让代码看起来更简洁,直观。
isolate
是Dart
平台对线程的实现方案,但和普通Thread
不一样的是,isolate
拥有独立的内存,isolate
由线程和独立内存构成。正是因为isolate
线程之间的内存不共享,因此isolate
线程之间并不存在资源抢夺的问题,因此也不须要锁。
经过isolate
能够很好的利用多核CPU,来进行大量耗时任务的处理。isolate
线程之间的通讯主要经过port
来进行,这个port
消息传递的过程是异步的。经过Dart
源码也能够看出,实例化一个isolate
的过程包括,实例化isolate
结构体、在堆中分配线程内存、配置port
等过程。
isolate
看起来其实和进程比较类似,以前请教阿里架构师宗心问题时,宗心也说过“isolate
的总体模型我本身的理解其实更像进程,而async
、await
更像是线程”。若是对比一下isolate
和进程的定义,会发现确实isolate
很像是进程。
下面是一个isolate
的例子,例子中新建立了一个isolate
,而且绑定了一个方法进行网络请求和数据解析的处理,并经过port
将处理好的数据返回给调用方。
loadData() async {
// 经过spawn新建一个isolate,并绑定静态方法
ReceivePort receivePort =ReceivePort();
await Isolate.spawn(dataLoader, receivePort.sendPort);
// 获取新isolate的监听port
SendPort sendPort = await receivePort.first;
// 调用sendReceive自定义方法
List dataList = await sendReceive(sendPort, 'https://jsonplaceholder.typicode.com/posts');
print('dataList $dataList');
}
// isolate的绑定方法
static dataLoader(SendPort sendPort) async{
// 建立监听port,并将sendPort传给外界用来调用
ReceivePort receivePort =ReceivePort();
sendPort.send(receivePort.sendPort);
// 监听外界调用
await for (var msg in receivePort) {
String requestURL =msg[0];
SendPort callbackPort =msg[1];
Client client = Client();
Response response = await client.get(requestURL);
List dataList = json.decode(response.body);
// 回调返回值给调用者
callbackPort.send(dataList);
}
}
// 建立本身的监听port,而且向新isolate发送消息
Future sendReceive(SendPort sendPort, String url) {
ReceivePort receivePort =ReceivePort();
sendPort.send([url, receivePort.sendPort]);
// 接收到返回值,返回给调用者
return receivePort.first;
}
复制代码
isolate
和iOS中的线程还不太同样,isolate
的线程更偏底层。当生成一个isolate
后,其内存是各自独立的,相互之间并不能进行访问。但isolate
提供了基于port
的消息机制,经过创建通讯双方的sendPort
和receiveport
,进行相互的消息传递,在Dart
中叫作消息传递。
从上面例子中能够看出,在进行isolate
消息传递的过程当中,本质上就是进行port
的传递。将port
传递给其余isolate
,其余isolate
经过port
拿到sendPort
,向调用方发送消息来进行相互的消息传递。
正如其名,Embedder
是一个嵌入层,将Flutter
嵌入到各个平台上。Embedder
负责范围包括原平生台插件、线程管理、事件循环等。
Embedder
中存在四个Runner
,四个Runner
分别以下。其中每一个Flutter Engine
各自对应一个UI Runner
、GPU Runner
、IO Runner
,但全部Engine
共享一个Platform Runner
。
Runner
和isolate
并非一码事,彼此相互独立。以iOS平台为例,Runner
的实现就是CFRunLoop
,以一个事件循环的方式不断处理任务。而且Runner
不仅处理Engine
的任务,还有Native Plugin
带来的原平生台的任务。而isolate
则由Dart VM
进行管理,和原平生台线程并没有关系。
Platform Runner
和iOS平台的Main Thread
很是类似,在Flutter
中除耗时操做外,全部任务都应该放在Platform
中,Flutter
中的不少API并非线程安全的,放在其余线程中可能会致使一些bug。
但例如IO之类的耗时操做,应该放在其余线程中完成,不然会影响Platform
的正常执行,甚至于被watchdog
干掉。但须要注意的是,因为Embedder Runner
的机制,Platform
被阻塞后并不会致使页面卡顿。
不仅是Flutter Engine
的代码在Platform
中执行,Native Plugin
的任务也会派发到Platform
中执行。实际上,在原生侧的代码运行在Platform Runner
中,而Flutter
侧的代码运行在Root Isolate
中,若是在Platform
中执行耗时代码,则会卡原平生台的主线程。
UI Runner
负责为Flutter Engine
执行Root Isolate
的代码,除此以外,也处理来自Native Plugin
的任务。Root Isolate
为了处理自身事件,绑定了不少函数方法。程序启动时,Flutter Engine
会为Root
绑定UI Runner
的处理函数,使Root Isolate
具有提交渲染帧的能力。
当Root Isolate
向Engine
提交一次渲染帧时,Engine
会等待下次vsync,当下次vsync到来时,由Root Isolate
对Widgets
进行布局操做,并生成页面的显示信息的描述,并将信息交给Engine
去处理。
因为对widgets
进行layout
并生成layer tree
是UI Runner
进行的,若是在UI Runner
中进行大量耗时处理,会影响页面的显示,因此应该将耗时操做交给其余isolate
处理,例如来自Native Plugin
的事件。
GPU Runner
并不直接负责渲染操做,其负责GPU相关的管理和调度。当layer tree
信息到来时,GPU Runner
将其提交给指定的渲染平台,渲染平台是Skia配置的,不一样平台可能有不一样的实现。
GPU Runner
相对比较独立,除了Embedder
外其余线程均不可向其提交渲染信息。
一些GPU Runner
中比较耗时的操做,就放在IO Runner
中进行处理,例如图片读取、解压、渲染等操做。可是只有GPU Runner
才能对GPU提交渲染信息,为了保证IO Runner
也具有这个能力,因此IO Runner
会引用GPU Runner
的context
,这样就具有向GPU提交渲染信息的能力。
简书因为排版的问题,阅读体验并很差,布局、图片显示、代码等不少问题。因此建议到我Github
上,下载Flutter编程指南 PDF
合集。把全部Flutter
文章总计三篇,都写在这个PDF
中,并且左侧有目录,方便阅读。
下载地址:Flutter编程指南 PDF 麻烦各位大佬点个赞,谢谢!😁