老铁们,大家好,相信还有很多朋友对于dart网站源码分享和dart-cms的相关问题不太懂,没关系,今天就由我来为大家分享分享dart网站源码分享以及dart-cms的问题,文章篇幅可能偏长,希望可以帮助到大家,下面一起来看看吧!
在Android应用中,用户时常会遇到界面卡顿的情况,非常影响用户的体验。作为Android开发肯定都知道:应用在主线程里做了大量的耗时操作(例如文件读写,数据库读写,网络访问,图片编解码等),就会导致UI不能及时刷新,出现跳帧现象。如果应用主线程不能及时处理用户的输入事件或广播消息,系统甚至会直接弹出著名的ANR对话框,提示用户杀死应用。
在Flutter应用中,如果出现界面卡顿,它的原因也是如此吗?
我们带着这些疑问,一起来搞清楚Flutter的线程模型和异步原理,并找到问题的答案。
一、Flutter系统结构
首先,我们熟悉下Flutter官方提供的系统结构图:
整体框架是采用分层设计的,自上而下分别是:Framework、Engine、Embedder。
Framework:基于Dart语言构建的Framework,包括了各种UI组件,动画和手势识别等,并将所有的设计通过Widgets小组件层进行抽象封装。所以在Flutter中一切都是Widget;Engine:基于C/C++构建的引擎,包括了Skia、Dart和Text等,实现了Flutter渲染引擎,文字排版,事件处理和Dart运行时等功能。Skia和Text为上层提供调用底层渲染和排版的功能,Dart运行时提供了调用Dart和渲染引擎的能力;Embedder:嵌入层是操作系统适配层,会将Flutter嵌入到各个平台上。嵌入层负责适配原生平台插件、线程管理、渲染Surface设置等。
从架构图中可以看到,Embedder负责线程的创建和管理,并且提供TaskRunner给Engine使用。Engine虽然自己并不创建和管理线程,但是它通过DartVM(DartRuntimeMgmt)提供Isolate给Framework和应用层进行多线程创建。
二、TaskRunner
接下来,我们继续了解Embeder提供的四个TaskRunner:PlatformRunner,UIRunner,GPURunner,IORunner。
Flutter的代码基本上由这四个Runner负责运行,每个Runner负责不同的任务,不只处理Engine的任务,还处理NativePlugin带来的原生平台任务。
在Android中,每个FlutterEngine各自拥有一个UIRunner、GPURunner、IORunner,但是一个应用中的所有Engine共享一个PlatformRunner。每个Runner都是一个平台线程,且Engine会将UIRunner和RootIsolate进行互相绑定。但Runner和Isolate本身是相互独立的,Isolate由DartVM进行管理,不由Runner管理。
PlatformRunner
PlatformRunner运行在平台的MainThread,负责执行FlutterEngine的代码和NativePlugin任务。
如果在PlatformRunner中运行耗时任务,会影响原生平台任务的正常执行。但是PlatformRunner被阻塞后并不会导致页面卡顿。因为PlatformRunner不负责Flutter的页面渲染,这点和Android原生应用不一样。
UIRunner
UIRunner负责为Engine执行RootIsolate的代码,而RootIsolate负责运行所有Dart代码。
RootIsolate绑定了很多UIRunner的处理函数,负责创建管理LayerTree最终绘制到屏幕上的内容,因此这个线程被阻塞会直接导致界面卡顿掉帧。
每当页面更新的vsync到来时,RootIsolate会对Widgets进行layout,生成Layertree等页面显示信息,提交给Engine去处理。
所以,在RootIsolate中运行耗时任务会导致页面显示卡顿。
GPURunner
GPURunner负责将UIRunner提供的LayerTree信息转化为平台可执行的GPU指令,并提交给渲染平台,如Skia。
GPURunner还负责管理绘制所需要的GPU资源,比如平台Framebuffer,Surface,Texture和Buffers等。
GPURunner相对比较独立,除了Embedder层Runner线程外,其他线程均不可向其提交渲染信息。
IORunner
IORunner负责将读取的图片解压转换成GPU能够处理的格式并提交给GPURunner处理。
当Image这样的资源通过asynccall调用时,Framework会通知IORunner进行图片的异步加载,进行格式处理,然后通过GPURunner的Context引用,提交给GPURunner处理。
由上可知,在AndroidFlutter应用中,如果出现界面卡顿,它的原因和Android应用的原因并不相同。Flutter应用中平台线程的阻塞不会影响界面的卡顿,而UIRunner的阻塞必然导致页面卡顿。
那么,在Flutter应用中,像网络请求,文件读取,海量计算,图片处理,编解码等耗时任务都应该怎么处理,才能不阻塞UIRunner呢?
要解释清楚这个问题,我们需要先了解Flutter中的线程模型:Isolate。
三、线程模型
Isolate是Dart平台对线程的实现方案,所以和线程一样,也可以利用多核CPU去处理大量耗时任务。
Isolate底层实际还是使用操作系统提供的OSThread。但和普通Thread不同,Isolate拥有独立的内存,由线程加独立内存构成。
由于Isolate线程之间内存不共享,所以Isolate线程之间并不存在共享数据的问题,所以也不需要Isolate数据同步机制。
Isolate之间虽然不能共享数据,但是可以通过端口Port的方式进行数据通信。
3.1Isolate
在AndroidFlutter应用启动后,会首先执行main函数,接着调用runApp,然后创建一个FlutterEngin。Engin会启动四个TaskRunner,并且UIRunner开始执行RootIsolate主线程中的代码。
Flutter应用默认在单线程中运行Dart代码,如果不开启新的线程,所有Dart代码都在RootIsolate主线程中运行。
我们可以通过Isolate的创建和Port数据通信的例子来进一步了解代码执行的线程情况:
//kiki_main_tab_page.dart\nclassKKMainTabPageStateextendsBaseThemeState<KKMainTabPage>withWidgetsBindingObserver{\n//这是摸鱼kik的主界面\n……\n\n@override\nvoidinitState(){\nsuper.initState();\n……\n\n//测试Future中代码的运行所属Isolate\nFuture.delayed(constDuration(milliseconds:500))\n.then((value){\nKKMethodChannelUtil.setAndroidStatusBarTheme(AppScreenMedia.isLight);\nprint_cjf(&39;+Isolate.current.debugName.toString());\n});\n\nprint_cjf(&39;+Isolate.current.debugName.toString());\n//测试在新的isolate中请求数据。此处没有用await等待方法的异步结果。\ntestIsolate();\nprint_cjf(&39;+Isolate.current.debugName.toString());\n}\n\n@override\nWidgetbuild(BuildContextcontext){\nprint_cjf(&39;+Isolate.current.debugName.toString());\n……\n}\n……\n}\n\n//test_isolate.dart\nvoidtestIsolate()async{\nprint_cjf(&39;+Isolate.current.debugName.toString());\n\n//创建ReceivePort,用来接受新Isolate发送的消息\nReceivePortreceivePort1=ReceivePort();\nprint_cjf(&39;+Isolate.current.debugName.toString());\n\n//创建新的Isolate,并且把receivePort1的发送端口传给newIsolate\nvarnewIsolate=awaitIsolate.spawn(dataLoader,receivePort1.sendPort);\nprint_cjf(&39;+Isolate.current.debugName.toString());\n\n//等待newIsolate发送的异步消息(异步消息只有newIsolate的发送端口receivePort3.sendPort)。\n//通过first获得异步消息后,会立即关闭receivePort1的sendPort端口。\nSendPortsendPort3=awaitreceivePort1.first;\nprint_cjf(&39;+Isolate.current.debugName.toString());\n\n//因为上面first函数获取异步消息后,关闭了receivePort1的sendPort端口\n//所以要新创建一个新的ReceivePort接受newIsolate发送过来的消息\nReceivePortreceivePort2=ReceivePort();\nprint_cjf(&39;+Isolate.current.debugName.toString());\n\n//使用newIsolate的发送端口sendPort3,发送异步消息(异步消息包括网络url,和rootIsolate的receivePort2发送端口)\nsendPort3.send([&39;,receivePort2.sendPort]);\nprint_cjf(&39;+Isolate.current.debugName.toString());\nsendPort3.send([&34;,receivePort2.sendPort]);\nprint_cjf(&39;+Isolate.current.debugName.toString());\nsendPort3.send([&34;,receivePort2.sendPort]);\nprint_cjf(&39;+Isolate.current.debugName.toString());\nsendPort3.send([&34;,receivePort2.sendPort]);\nprint_cjf(&39;+Isolate.current.debugName.toString());\n\n//获取newIsolate发送来的异步消息(异步获取的网络数据)\n//方法一:不等待循环获取异步数据\nreceivePort2.listen((msg){\nprint_cjf(&39;+Isolate.current.debugName.toString()+&39;);\n});\n\nprint_cjf(&39;+Isolate.current.debugName.toString());\n}\n\nvoiddataLoader(SendPortsendPort1)async{\nprint_cjf(&39;+Isolate.current.debugName.toString());\n\nReceivePortreceivePort3=ReceivePort();\nsendPort1.send(receivePort3.sendPort);\n\nprint_cjf(&39;+Isolate.current.debugName.toString());\n\nawaitfor(varmsginreceivePort3){\nStringdataUrl=msg[0];\nSendPortsendPort2=msg[1];\n\n//暂不从网络中获取数据,直接模拟一个数据返回\nsendPort2.send(&39;+Isolate.current.debugName.toString()+&34;);\nprint_cjf(&39;+Isolate.current.debugName.toString()+&34;);\n\nif(dataUrl.startsWith(&34;)){\n//销毁当前isolate,并发送结束消息给sendPort2\nIsolate.exit(sendPort2,&34;);\n}\n}\n\nprint_cjf(&39;+Isolate.current.debugName.toString());//不会被执行到。\n}\n\n//输出log\nI/flutter(4910):11:25:56:587:cjf—initState2—main//KKMainTabPageState的initState,开始调用\nI/flutter(4910):11:25:56:588:cjf—testIsolatestart—main\nI/flutter(4910):11:25:56:589:cjf—testIsolate1—main//调用awaitIsolate.spawn创建新isolate,testIsolate方法中断执行,需等待异步结果回来后恢复执行。\nI/flutter(4910):11:25:56:590:cjf—initState3—main//KKMainTabPageState的initState,awaitIsolate.spawn会进行异步调度,所以会返回到initState中继续执行\nI/flutter(4910):11:25:56:591:cjf—build4—main//KKMainTabPageState的build,后续还会被多次调用\n\nI/flutter(4910):11:25:56:592:cjf—testIsolate2—main//异步执行,awaitIsolate.spawn的异步结果返回,testIsolate后续代码继续执行\nI/flutter(4910):11:25:57:086:cjf—initState1—main//异步执行,Future.delayed时间到了后,异步任务会得到执行,且运行在RootIsolate中\n\nI/flutter(4910):11:25:57:133:cjf—dataLoaderstart—dataLoader//新建isolate内部,新的isolate开始运行\nI/flutter(4910):11:25:57:134:cjf—dataLoader1—dataLoader//新建isolate内部,将自己的SendPort3发送给RootIsolate的SendPort1\n\nI/flutter(4910):11:25:57:140:cjf—testIsolate3—main//异步执行,awaitreceivePort1.first,仍然在rootisolate中\nI/flutter(4910):11:25:57:141:cjf—testIsolate4—main\n\nI/flutter(4910):11:25:57:142:cjf—testIsolate5.1—main//给SendPort3发送jsonplaceholder.typicode.com数据和sendPort2\nI/flutter(4910):11:25:57:144:cjf—testIsolate5.2—main//给SendPort3发送w.sohu.com数据和sendPort2\nI/flutter(4910):11:25:57:144:cjf—dataLoader3.1—dataLoader—https://jsonplaceholder.typicode.com/posts/1\nI/flutter(4910):11:25:57:144:cjf—testIsolate5.3—main//给SendPort3发送aaaaaa数据和sendPort2\nI/flutter(4910):11:25:57:145:cjf—dataLoader3.1—dataLoader—https://w.sohu.com/detail/1\nI/flutter(4910):11:25:57:145:cjf—testIsolate5.4—main//给SendPort3发送bbbbbb数据和sendPort2\nI/flutter(4910):11:25:57:146:cjf—testIsolateend—main//设置listen数据,退出testIsolate方法\nI/flutter(4910):11:25:57:146:cjf—dataLoader3.1—dataLoader—aaaaaa\n\nI/flutter(4910):11:25:57:148:cjf—testIsolate6.2—main—dataLoader2.1—dataLoader—https://jsonplaceholder.typicode.com/posts/1\nI/flutter(4910):11:25:57:149:cjf—testIsolate6.2—main—dataLoader2.1—dataLoader—https://w.sohu.com/detail/1\nI/flutter(4910):11:25:57:149:cjf—testIsolate6.2—main—dataLoader2.1—dataLoader—aaaaaa\nI/flutter(4910):11:25:57:150:cjf—testIsolate6.2—main—dataLoader4—IsolateexitwithlastmessagetosendPort2\n\nI/flutter(4910):11:25:57:588:cjf—build4—main//KKMainTabPageState的build,被多次调用\n\n
可以从上述例子,Log和注释可以看到:
RootIsolate的debugName也是&34;,而不是&34;;通过Isolate.spawn创建新的Isolate,debugName即为方法名&34;;除了新建Isolate中的代码,其它Dart代码都默认运行在RootIsolate中,包括widget中的代码,Future中的代码,和await异步方法中的代码。如:&34;,&34;,&34;,&34;都在&34;线程中运行;await方法调用,需要等待异步任务结果,会停止当前方法后续代码的执行,但不会阻塞调用者后续代码的执行,如log&34;的输出并没有等待log&34;的输出。包括widget的构建log&34;,和initState中的Futuredelay异步任务的log&34;都得到了执行,而不是一直阻塞等待testIsolate方法中的awaitIsolate.spawn和awaitreceivePort1.first语句的返回;Isolate之间通过ReceivePort进行信息发送和接送,且消息传递的过程是异步的。如,log&34;,&34;,&34;,&34;和log&34;是穿插着打印出来的;Isolate之间可以通过awaitreceivePort1.first来阻塞式接收一次消息,或通过receivePort2.listen来非阻塞式循环接收信息,或通过awaitforreceivePort3来阻塞式循环接收信息。
从例子中我们不但可以看到Isolate的创建和Port数据通信方式,同时也看到了Dart代码都默认运行在RootIsolate主线程中。
3.2compute
上面例子中创建一个Isolate进行数据通讯,步骤较为麻烦。为此Flutter为我们提供了一个简版的Isolate:compute。
compute很适合简单,高CPU的耗时任务处理,它可以很便捷地进行多线程任务开发,我们举例进行说明:
//test_isolate.dart\nvoidtestCompute()async{\nprint_cjf(&39;+Isolate.current.debugName.toString());\n\nFuture.delayed(newDuration(seconds:1),(){\nprint_cjf(&39;+Isolate.current.debugName.toString());\n});\n\nvarcount=awaitcompute(countTask,1234567890);\n\nprint_cjf(&39;+Isolate.current.debugName.toString()+&34;+count.toString());\n}\n\nintcountTask(intnum1){\nprint_cjf(&39;+Isolate.current.debugName.toString());\n\nintcount=0;\nwhile(num1>0){\nif(num1%2==0){count++;}\nnum1–;\n}\n\nprint_cjf(&39;+Isolate.current.debugName.toString());\n\nreturncount;\n}\n\n//输出Log\nI/flutter(4910):12:44:32:731:cjf—testComputestart—main\nI/flutter(4910):12:44:33:254:cjf—countTaskstart—Closure:(int)=>intfromFunction&39;:static.\nI/flutter(4910):12:44:33:736:cjf—testCompute1—main\nI/flutter(4910):12:45:16:329:cjf—countTaskend—Closure:(int)=>intfromFunction&39;:static.\nI/flutter(4910):12:45:16:330:cjf—testComputeend—main—count=617283945\n\n
compute方法是Flutterfoundation包中的顶层方法,它对Isolate.spawn及sendPort消息发送做了封装,返回一个异步结Future<R>。
通过封装后的接口,我们只需简单调用awaitcompute,就可以等待一个耗时43s的计算任务的异步执行结果,并且等待过程中没有阻塞RootIsolate中其它异步任务地执行,比如,通过Future.delayed插入的异步任务&34;,在countTaskIsolate执行的同时,在RootIsolate中被执行了。
3.3Memory
上面2个例子,要么是直接等待新Isolate的异步结果,要么是通过port进行数据传输,并没有使用全局数据进行共享,所有也没有数据同步问题。
实际上在Flutter中确实没有类似Android的全局共享数据,因为Flutter中的Isolate拥有独立的内存,数据没法共享,只能通过port传输。
我们继续通过一个简单的例子,来说明Isolate独立内存这个特点:
//test_isolate.dart\nintintValue=0;//定义普通int顶层变量\nIntObjectintObject=IntObject();//定义对象IntObject顶层变量\n\nclassIntObject{\nint_i=0;\nvoidincrease(){_i++;}\nintget(){return_i;}\n}\n\nvoidtestIsolateMemory()async{\nprint_cjf(&39;);\nfinalreceive=ReceivePort();\n\nreceive.listen((msg){\n//打印MemoryTaskIsolate传过来的String\nprint_cjf(&39;+&34;);\n//打印RootIsolate中的变量值\nprint_cjf(&39;+&34;);\n});\n\n//5s后,给顶层变量加1\nFuture.delayed(constDuration(seconds:5),(){\nintValue++;\nintObject.increase();\nprint_cjf(&39;+&34;);\n});\n\nIsolateisolate=awaitIsolate.spawn(MemoryTask,receive.sendPort);\n\nprint_cjf(&39;);\n}\n\nvoidMemoryTask(SendPortsendPort){\nintcounter=0;//MemoryTaskIsolate中的局部变量\nprint_cjf(&39;);\n\n//每隔1s,给顶层变量和局部变量都加1\nTimer.periodic(constDuration(seconds:1),(_){\ncounter++;\nintValue++;\nintObject.increase();\n\nStringsendMsg=&34;;\n//打印MemoryTaskIsolate中的变量值\nprint_cjf(&39;);\nsendPort.send(sendMsg);\n\nif(counter>=10){\n//销毁当前isolate,并发送结束消息给sendPort\nIsolate.exit(sendPort,&34;);\n}\n});\n\nprint_cjf(&39;);\n}\n\n//输出Log\nI/flutter(4910):12:58:21:555:cjf—testIsolateMemorystart\nI/flutter(4910):12:58:21:575:cjf—testIsolateMemoryend\nI/flutter(4910):12:58:22:84:cjf—MemoryTaskstart\nI/flutter(4910):12:58:22:86:cjf—MemoryTaskend\n\nI/flutter(4910):12:58:23:93:cjf—MemoryTask—counter=1,i=1,intObject=1//MemoryTaskIsolate中的顶层\n变量发生改变\nI/flutter(4910):12:58:23:96:cjf—testIsolateMemory—data===counter=1,i=1,intObject=1\nI/flutter(4910):12:58:23:97:cjf—testIsolateMemory—i=0,intObject=0//RootIsolate中的顶变量没有改变\nI/flutter(4910):12:58:24:87:cjf—MemoryTask—counter=2,i=2,intObject=2\nI/flutter(4910):12:58:24:89:cjf—testIsolateMemory—data===counter=2,i=2,intObject=2\nI/flutter(4910):12:58:24:90:cjf—testIsolateMemory—i=0,intObject=0//RootIsolate中的顶变量没有改变\nI/flutter(4910):12:58:25:89:cjf—MemoryTask—counter=3,i=3,intObject=3\nI/flutter(4910):12:58:25:90:cjf—testIsolateMemory—data===counter=3,i=3,intObject=3\nI/flutter(4910):12:58:25:91:cjf—testIsolateMemory—i=0,intObject=0//RootIsolate中的顶变量没有改变\nI/flutter(4910):12:58:26:89:cjf—MemoryTask—counter=4,i=4,intObject=4\nI/flutter(4910):12:58:26:90:cjf—testIsolateMemory—data===counter=4,i=4,intObject=4\nI/flutter(4910):12:58:26:91:cjf—testIsolateMemory—i=0,intObject=0//RootIsolate中的顶变量没有改变\n\nI/flutter(4910):12:58:26:621:cjf—testIsolateMemory—delayed:i=1,intObject=1//RootIsolate中给顶层变量加1\n\nI/flutter(4910):12:58:27:88:cjf—MemoryTask—counter=5,i=5,intObject=5\nI/flutter(4910):12:58:27:90:cjf—testIsolateMemory—data===counter=5,i=5,intObject=5\nI/flutter(4910):12:58:27:90:cjf—testIsolateMemory—i=1,intObject=1//RootIsolate中的顶层变量发生改变\n\nI/flutter(4910):12:58:28:89:cjf—MemoryTask—counter=6,i=6,intObject=6\nI/flutter(4910):12:58:28:90:cjf—testIsolateMemory—data===counter=6,i=6,intObject=6\nI/flutter(4910):12:58:28:91:cjf—testIsolateMemory—i=1,intObject=1\nI/flutter(4910):12:58:29:87:cjf—MemoryTask—counter=7,i=7,intObject=7\nI/flutter(4910):12:58:29:89:cjf—testIsolateMemory—data===counter=7,i=7,intObject=7\nI/flutter(4910):12:58:29:90:cjf—testIsolateMemory—i=1,intObject=1\nI/flutter(4910):12:58:30:89:cjf—MemoryTask—counter=8,i=8,intObject=8\nI/flutter(4910):12:58:30:91:cjf—testIsolateMemory—data===counter=8,i=8,intObject=8\nI/flutter(4910):12:58:30:92:cjf—testIsolateMemory—i=1,intObject=1\nI/flutter(4910):12:58:31:89:cjf—MemoryTask—counter=9,i=9,intObject=9\nI/flutter(4910):12:58:31:91:cjf—testIsolateMemory—data===counter=9,i=9,intObject=9\nI/flutter(4910):12:58:31:91:cjf—testIsolateMemory—i=1,intObject=1\nI/flutter(4910):12:58:32:89:cjf—MemoryTask—counter=10,i=10,intObject=10\nI/flutter(4910):12:58:32:90:cjf—testIsolateMemory—data===counter=10,i=10,intObject=10\nI/flutter(4910):12:58:32:91:cjf—testIsolateMemory—i=1,intObject=1\n\nI/flutter(4910):12:58:32:91:cjf—testIsolateMemory—data===MemoryTask—IsolateexitwithlastmessagetosendPort\nI/flutter(4910):12:58:32:92:cjf—testIsolateMemory—i=1,intObject=1\n\n
我们从输出log可以看出,
MemoryTaskIsolate中,每隔1s就给顶层变量加1,但是,RootIsolate中的变量并不会同步改变;RootIsolate中5s后给顶层变量加1,也只影响了RootIsolate中读取的顶层变量值,不会影响到MemoryTaskIsolate中读取的顶层变量值。
由此可见,跨Isolate数据共享只能通过port的方式。Isolate中的内存是独立的,它们不存在数据共享,当然也就不需要处理共享数据的线程同步问题。
Isolate中内存独立的特点,和Java中的线程局部存储ThreadLocal有点类似。Android中Looper类里的静态变量sThreadLocal,就属于线程局部存储,不同线程中调用Looper.prepare设置到sThreadLocal中的Looper对象各不相同,取出来使用的对象也不相同。我们可以参考对比其源码,如下:
//Looper.java\npublicfinalclassLooper{\nstaticfinalThreadLocal<Looper>sThreadLocal=newThreadLocal<Looper>();\n……\nprivatestaticvoidprepare(booleanquitAllowed){\nif(sThreadLocal.get()!=null){\nthrownewRuntimeException(&34;);\n}\nsThreadLocal.set(newLooper(quitAllowed));\n}\n……\n}\n
通过Dart源码也可以看到,Isolate的创建过程,包括了创建Isolate结构体,在堆中分配线程内存,创建线程和使用线程池等代码,也进一步说明了Isolate是具有独立内存的线程。
了解完Isolate线程模型,我们就明白了:我们所写的Dart代码默认是运行在RootIsolate中,而RootIsolate是运行在UIRunner上的,所以如果我们写的Dart代码过于耗时,必然导致负责管理绘制的UIRunner不能及时刷新页面,导致页面卡顿。因此,在Flutter应用中也需要使用多线程去处理耗时任务。
但是已有的使用经验中,我们并没有单独创建Isolate去处理网络请求任务,这与上述结论不就矛盾了吗?
我们先继续学习Flutter的异步原理,再来解答这个问题。
四、异步原理
Flutter中,如果不单独创建Isolate的话,可以说是单线程模型,它通过单线程异步方案支持大量并发操作。
大家容易把异步,并行和并发这几个概念搞混。我们先来了解下并行和并发的概念:
并行:指的是多个CPU,在同一时间里执行多个任务;并发:指的是一个CPU,轮换着去处理多个任务,由系统来管理任务的切换。
并行的实现,必须由多线程来完成。多线程的方式可以利用多CPU的并行优势,同时执行多个任务。一般通过线程池来管理和复用大量线程。
并发的实现,多采用单线程+非阻塞+事件通知的方式来完成。因为线程切换的消耗是比较大的,不适合大量创建,所以并发的实现多采用单线程。阻塞式中断是在执行任务时将线程阻塞,等待执行完成后再恢复线程执行,无法达到异步效果。而非阻塞式中断是在执行任务时,保存当前上下文,不等待任务结果,继续调度当前线程的其它任务。调度当前线程的其它任务就依赖于事件通知。
而异步是相对于同步来说,它指程序的执行顺序与任务的排列顺序是不一致的。异步属于并发,不属于并行。
上面的描述,可能比较抽象,不易理解,我们继续通过图和示例来学习Flutter的异步原理。
4.1事件循环和消息队列
在Flutter中,Isolate是通过事件循环和消息队列来实现异步的。每个Isolate包含一个事件循环以及两个事件队列:
EventLoop:事件循环,负责无限循环读取微任务队列和事件队列进行处理;Microtaskqueue:微任务事件队列,优先级比Eventqueue高,应用可以向Isolate添加微任务;Eventqueue:普通事件队列,包括IO事件,绘制事件,手势事件,及应用添加的外部事件。
事件循环和事件队列的执行流程如下图:
在RootIsolate中,Eventqueue包括了绘制事件和手势事件,如果它们不能得到及时处理,会导致渲染、手势响应延时,出现卡顿现象。
为了保证渲染和手势得到及时响应,我们应该尽量不要向Microtaskqueue中添加事件,因为它的处理优先级比Eventqueue要高。
即便向Eventqueue中添加事件时,也不能添加过于耗时的事件,避免影响后续的渲染和手势事件得不到及时响应,影响用户使用体验。
我们可以通过Future和await向Eventqueue中插入任务,也可以通过scheduleMicrotask向Microtaskqueue添加任务。
下面通过举例,进一步进行说明:
//test_isolate.dart\nvoidtestMicroTask()async{\nprint_cjf(&39;);\n\nnewFuture(()=>print_cjf(&39;))//创建异步任务future1\n.then((_){\nnewFuture(()=>print_cjf(&39;));//创建异步任务future2\nscheduleMicrotask(()=>print_cjf(&39;));//创建异步任务microtask3。比同层的future2先执行\n\nprint_cjf(&39;);\n})\n.then((_)=>print_cjf(&39;));\n\nscheduleMicrotask(()=>print_cjf(&39;));//创建异步任务microtask4。比同级别的future1先执行\n\nprint_cjf(&39;);\n\nawaitfunDelay();//创建异步任务funDelay。调用耗时方法,使用await等待其结果返回\n\nprint_cjf(&39;);//异步执行\n}\n\nFuture<int>funDelay()async{//声明异步方法,必须使用async\nprint_cjf(&39;);//同步执行\n\nawaitFuture.delayed(Duration(seconds:1));//见log1,调用1s耗时方法,使用await等待\n//awaitFuture.delayed(Duration(milliseconds:1));//见log2,调用1ms耗时方法,使用await等待\n\nprint_cjf(&39;);//异步执行\nreturn2;\n}\n\n//输出log1\nI/flutter(4910):17:51:25:216:cjf—testMicroTaskstart\nI/flutter(4910):17:51:25:216:cjf—testMicroTask1//同步执行\nI/flutter(4910):17:51:25:216:cjf—funDelaystart//同步执行\n\nI/flutter(4910):17:51:25:217:cjf—microtask4//第一层microtask\nI/flutter(4910):17:51:25:217:cjf—future1-1//第一层future\nI/flutter(4910):17:51:25:218:cjf—future1-2//第一层future的链式调用\nI/flutter(4910):17:51:25:218:cjf—future1-3//第一层future的链式调用\n\nI/flutter(4910):17:51:25:218:cjf—microtask3//第二层microtask\nI/flutter(4910):17:51:25:218:cjf—future2//第二层future\n\nI/flutter(4910):17:51:26:218:cjf—funDelayend//属于第一层future,但由于时间条件不满足,比第二层future更后执行了\nI/flutter(4910):17:51:26:219:cjf—testMicroTaskend//属于第一层future\n\n//输出log2\nI/flutter(9669):17:58:34:948:cjf—testMicroTaskstart\nI/flutter(4910):17:58:34:950:cjf—testMicroTask1//同步执行\nI/flutter(9669):17:58:34:950:cjf—funDelaystart//同步执行\n\nI/flutter(9669):17:58:34:951:cjf—microtask4//第一层microtask\nI/flutter(9669):17:58:35:94:cjf—future1-1//第一层future\nI/flutter(9669):17:58:35:95:cjf—future1-2//第一层future的链式调用\nI/flutter(9669):17:58:35:96:cjf—future1-3//第一层future的链式调用\n\nI/flutter(9669):17:58:35:96:cjf—microtask3//第二层microtask\n\nI/flutter(9669):17:58:35:97:cjf—funDelayend//属于第一层future\nI/flutter(9669):17:58:35:97:cjf—testMicroTaskend//属于第一层future\n\nI/flutter(9669):17:58:35:97:cjf—future2//第二层future\n
上述例子中,在funDelay等待1s和1ms的情况,分别输出了log1和log2。通过对比输出的log,我们可以清晰的看到:
Microtask和Future添加的任务都会被异步执行;同层的Microtask会比Future优先被处理。因为newFuture创建的异步事件被添加到了Eventqueue,而scheduleMicrotask创建的异步事件被添加到了MicroTaskqueue中,而事件循环总是会优先处理MicroTaskqueue中的事件;awaitfunDelay后的代码&34;,属于第一层future;await的运行受返回结果的时机影响:如log1中,awaitfunDelay需要1s时,事件循环处理到它时,发现时间条件不满足后跳过,所以&34;会比第二层的&34;后执行;如log2中,如果awaitfunDelay只需要1ms时,&34;就会比第二层的&34;先执行;但无论是log1还是log2,&34;都会比第二层的&34;后执行,因为microtask总是会被优先执行,即使更后被添加到microtask队列。
在上述例子中,awaitfunDelay调用也会将后续代码处理事件,如&34;,添加到Eventqueue,属于第一层事件,会比第二层事件&34;在Eventqueue中的位置更靠前,如果条件符合的话,会比第二层的&34;事件更先被执行。
&34;虽然是第二层MicroTask,但是在第一层&34;执行时被添加到Microtaskqueue中。当&34;执行完,回到Eventloop循环时,会优先处理Microtaskqueue中的事件,所以第二层的&34;会比第一层的&34;先执行。
下面用图来更形象地描述上述例子,我们可以更清晰的看到事件队列的添加和消费情况:
通过上面的示例,我们可以看到在Flutter中进行异步调用,一般需要用到三个关键词:Future,async,await。其中async和await需要一起使用。
Future:Future表示异步操作的返回结果,可以通过then处理返回结果。和Java中的Future功能差别巨大,注意区别使用;async:标记某个方法为异步方法,在声明方法的时候使用。其返回值是Future类型;await:表示等待某个异步方法的结果,一般调用耗时异步方法时使用。
下面我们继续深入Future,async,await的使用和原理学习。
4.2Future
在Dart中可以通过Future进行异步操作,Future异步的特点是提供了链式调用,可以解决回调地狱,异常捕获,和代码执行依赖等问题。
Future<T>表示一个指定类型的异步操作结果,如果不需要结果可以使用Future<void>。我们可以直接使用Future创建一个异步操作结果,也可以通过调用async方法得到一个异步操作结果。
当创建一个异步事件时,会直接返回一个Future,后续代码可以继续执行,不会被阻塞;当Future中的结果返回时,如果注册了then结果回调,onValue回调会拿到成功的返回值;当Future中出现执行异常时,如果注册了catchError失败回调,onError回调会拿到失败的异常信息和错误栈。
我们通过一个简单的例子,进一步了解Future:
//test_isolate.dart\nvoidtestFuture()async{\nprint_cjf(&39;);\n\nFuturef1=newFuture((){\nprint_cjf(&39;);\nreturn&34;;})\n.then((value){\nprint_cjf(&39;+&34;);\n//throw&39;;//见log2-1,log2-2\nreturn&34;;//见log1\n})\n.then((value)=>print_cjf(&39;+&34;))\n.catchError((err,stackTrace){\nprint_cjf(&39;+&34;);\nreturn&34;;\n},test:(err){\nprint_cjf(&39;);\nreturntrue;//见log2-1。testreturntrue:onError会执行,后续then的callback也会被调用,不会抛出UnhandledException\n//returnfalse;//见log2-2。testreturefalse:onError不会执行,后续then的callback不会被调用,且抛出[ERROR:flutter/runtime/dart_vm_initializer.cc(41)]UnhandledException:bbbError!。\n});\n\nprint_cjf(&39;);\n\nFuturef2=newFuture(()=>print_cjf(&39;));\nFuturef3=newFuture(()=>print_cjf(&39;));\n\nprint_cjf(&39;);\n\nf3.then((value)=>print_cjf(&39;+&34;));\nf2.then((value)=>print_cjf(&39;+&34;));\nf1.then((value)=>print_cjf(&39;+&34;));\n\nprint_cjf(&39;);\n}\n\n//输出log1\nI/flutter(22807):19:5:32:221:cjf—testFuturestart//同步执行\nI/flutter(22807):19:5:32:222:cjf—testFuture1//同步执行\nI/flutter(22807):19:5:32:223:cjf—testFuture2//同步执行\nI/flutter(22807):19:5:32:223:cjf—testFutureend//同步执行\n\nI/flutter(22807):19:5:32:225:cjf—future1-1//异步执行\nI/flutter(22807):19:5:32:226:cjf—future1-2—aaa//第一步返回的结果\nI/flutter(22807):19:5:32:228:cjf—future1-3—bbb//第二步返回的结果\nI/flutter(22807):19:5:32:229:cjf—future1-4—null//then后调用,却先执行。没有异常,第三步没有返回,默认为null\n\nI/flutter(22807):19:5:32:231:cjf—future2-1\nI/flutter(22807):19:5:32:233:cjf—future2-2—null//then后调用,却先执行\nI/flutter(22807):19:5:32:235:cjf—future3-1\nI/flutter(22807):19:5:32:236:cjf—future3-2—null//then先调用,后先执行\n\n//输出log2-1\nI/flutter(22807):19:6:58:613:cjf—testFuturestart\nI/flutter(22807):19:6:58:615:cjf—testFuture1\nI/flutter(22807):19:6:58:615:cjf—testFuture2\nI/flutter(22807):19:6:58:615:cjf—testFutureend\n\nI/flutter(22807):19:6:58:617:cjf—future1-1\nI/flutter(22807):19:6:58:618:cjf—future1-2—aaa\nI/flutter(22807):19:6:58:619:cjf—TestbbbError!//test返回ture,onError会执行\nI/flutter(22807):19:6:58:621:cjf—CaughtbbbError!—1testFuture.<anonymousclosure>(package:supermarie/testPages/test_isolate.dart:276:20)\nI/flutter(22807):<asynchronoussuspension>\nI/flutter(22807):19:6:58:621:cjf—future1-4—ccc//onError捕获异常后,返回结果作为下一步then的入参\n\nI/flutter(22807):19:6:58:622:cjf—future2-1\nI/flutter(22807):19:6:58:623:cjf—future2-2—null\nI/flutter(22807):19:6:58:624:cjf—future3-1\nI/flutter(22807):19:6:58:625:cjf—future3-2—null\n\n//输出log2-2\nI/flutter(22807):19:7:48:994:cjf—testFuturestart\nI/flutter(22807):19:7:48:996:cjf—testFuture1\nI/flutter(22807):19:7:48:997:cjf—testFuture2\nI/flutter(22807):19:7:48:998:cjf—testFutureend\n\nI/flutter(22807):19:7:49:0:cjf—future1-1\nI/flutter(22807):19:7:49:2:cjf—future1-2—aaa\nI/flutter(22807):19:7:49:4:cjf—TestbbbError!//test返回false,onError不会执行,后续的then回调也不会被执行,&34;不会被打印出来\nE/flutter(22807):[ERROR:flutter/runtime/dart_vm_initializer.cc(41)]UnhandledException:bbbError!\nE/flutter(22807):1testFuture.<anonymousclosure>(package:supermarie/testPages/test_isolate.dart:276:20)\nE/flutter(22807):<asynchronoussuspension>\nE/flutter(22807):\nI/flutter(22807):19:7:49:7:cjf—future2-1\nI/flutter(22807):19:7:49:8:cjf—future2-2—null\nI/flutter(22807):19:7:49:10:cjf—future3-1\nI/flutter(22807):19:7:49:12:cjf—future3-2—null
从例子,我们可以看到:
所有Future创建的任务都是异步执行的,即创建时机和运行时机不同。上例的所有log中,&34;都在&34;之后执行;future创建异步任务的次序,就是其加入到Eventqueue事件队列的次序,也是被异步执行的次序。输出log可以看到&34;,&34;,&34;都是按照创建次序执行;future通过then来获取异步结果,then支持链式调用。then注册的onValue结果回调,会在异步结果返回后立即执行,且后面的then的入参即是前面then回调的返回值。输出log1中&34;打印了&34;中的返回值,而&34;打印了&34;中的返回值;future注册then结果回调的时机,不影响其被回调的时机。见log1,&34;,&34;,&34;的执行次序,和其调用次序没有关系,只和其依赖的future的创建次序有关;Future通过catchError注册的onError回调,受test回调的返回值影响,我们对比log2-1和log2-2看:如果test回调返回true,异常会被onError处理,并返回值作为下一步then的入参;如果test回调返回false,异常不会被onError处理,还会导致后续Then回调不被执行,如&34;不会被执行到。
Future的链式调用,很好的解决了调用依赖导致的回调嵌套问题,我们以Kotlin中的登入流程为例做对比,源码如下:
//TestCallbackActivity.kt\nclassTestCallbackActivityextendsBaseActivity{\n//登入\nfunlogin(name:String,pwd:String,onSuccess:(uid:String)->Unit,onError:()->Unit):Unit{}\n//获取用户信息,用户首选tab\nfungetUserInfo(uid:String,onSuccess:(UserBean)->Unit,onError:()->Unit):Unit{}\n//显示用户信息\nfunshowUserInfo(user:UserBean):Unit{}\n//获取用户首选tab动态流\nfungetFeedListByTab(tab:Int,onSuccess:(feedList:List<NewFeedBean>)->Unit,onError:()->Unit):Unit{}\n//显示用户首选tab动态流\nfunshowFeedList(feedList:List<NewFeedBean>):Unit{}\n//获取用户第一个动态音乐\nfungetBackgroudMusic(mid:String,onSuccess:(playUrl:String)->Unit):Unit{}\n//播放用户动态音乐\nfunplayMusic(playUrl:String):Unit{}\n\noverridefunonResume(){\nlogin(&34;,&34;,onSuccess={\n//第一层回调,登入成功\ngetUserInfo(it,onSuccess={\n//第二层回调,获取用户信息成功\nshowUserInfo(it)\ngetFeedListByTab(it.mainTab,onSuccess={\n//第三层回调,获取用户主页feed流成功\nshowFeedList(it)\ngetBackgroudMusic(it[0].mid){\n//第四层回调,获取tab流默认背景音乐成功\nplayMusic(it)\n}\n},\nonError={})},\nonError={})},\nonError={})\n}\n}\n
因为登入,获取用户信息,获取默认主页feed流,和主获取页流背景音乐这几个流程是有强依赖关系的,后面的步骤依赖前一个步骤返回的参数,导致每多一个步骤,就会多一层回调嵌套,非常影响可读性。
同样的逻辑,我们再通过Flutter的Future实现,见如下源码。
//testFutureCallback.dart\n\n//登入\nFuture<String>login(Stringname,Stringpwd){}\n//获取用户信息,用户首选tab\nFuture<UserBean>getUserInfo(Stringuid){}\n//显示用户信息\nvoidshowUserInfo(UserBeanuser){}\n//获取用户首选tab动态流\nFuture<List<NewFeedBean>>getFeedListByTab(Inttab){}\n//显示用户首选tab动态流\nvoidshowFeedList(List<NewFeedBean>feedList){}\n//获取用户第一个动态音乐\nFuture<String>getBackgroudMusic(Stringmid){}\n//播放用户动态音乐\nvoidplayMusic(StringplayUrl){}\n\nvoidtestFuture2()async{\nnewFuture((){\nreturnlogin(&34;,&34;);\n}).then((uid){\nreturngetUserInfo(uid);\n}).then((user){\nshowUserInfo(user);\nreturngetFeedListByTab(user.mainTab);\n}).then((feedList){\nshowFeedList(feedList)\nreturngetBackgroudMusic(feedList[0].mid)\n}).then((playUrl){\nplayMusic(playUrl)\n}).catchError((err,stackTrace){\n//根据err信息,处理异常\n});\n}\n\n
可以看到,Future通过then明确代码块执行的依赖关系,不但消除了多层回调嵌套,也简化了方法的定义和错误处理。当然Kotlin中也可以通过协程等方式解决回调嵌套问题。
4.3async和await
在Dart中还可以通过async和await实现异步操作。async表示开启一个异步操作,可以返回一个Future结果。如果没有返回值,则默认返回一个Future<void>。
async、await本质上就是Dart对异步操作的一个语法糖,可以减少异步调用的嵌套调用。这个语法是在ES7标准中推出的,Dart中的设计和JS相同,是同步风格的异步实现。
我们通过数据加载的示例,来进一步了解async,await操作:
class_DemoPageStateextendsState<DemoPage>{\nListwidgets=[];\n\n@override\nvoidinitState(){\nsuper.initState();\nloadData();\nprint(&34;);\n}\n\nFuture<String>loadData()async{\nprint(&34;);\nResponseresponse=awaitgetNetData();//位置1\nsetState((){\nwidgets=json.decode(response.body);//位置2\n});\nreturnresponse.body;\n}\n\nFuture<Response>getNetData()async{\nStringrequestURL=&39;;\nClientclient=Client();//位置3\n\nFuture<Response>response=client.get(requestURL);//io异步,后续章节会详细分析\nreturnresponse;//位置4\n}\n}\n
在代码示例中,执行到loadData方法时,会同步进入方法内部进行执行,当执行到位置1的await时,就会停止loadData方法内后续的代码的执行,返回到外部调用者initState继续执行后面的print语句。当位置1的await有返回后,会从位置1处继续执行response的赋值操作及后续的setState位置2的语句。
那么,await是怎么做到阻塞当前方法,却不阻塞调用者继续运行的呢?以及,getNetData方法中,位置3处的代码是被同步执行的,还是被异步执行的呢?
先来分析下第一个问题,我们知道在程序执行过程中,离开当前的调用位置有两种方式:
执行return返回,当前函数在调用栈中的局部变量、形参等状态会被销毁;继续调用其他函数,需要保先存当前函数的变量和执行位置,其它函数调用返回后再恢复变量继续执行。保存变量一般有2种方式:一种,会将变量继续保存在栈区,在回到指针指向的离开位置时,会继续从栈中取出变量使用;另一种,则在执行当前函数时就将变量直接分配到堆区,当再次回到当前位置时,还会继续从堆区中获取变量。async,await就属于这种方式。
在Flutter中,async函数也有自己的上下文环境。当执行到await时,会保存当前的上下文,并将当前位置标记为待处理任务,并将待处理任务放入当前Isolate的event队列中。在每个事件循环时询问这个任务是否满足执行条件,如果需要进行处理,就恢复上下文,从上次离开的位置继续执行。
所以说,await并没有开启新的Isolate,只是把await后的代码封装成待处理任务,放到当前Isolate的消息队列中,然后继续运行当前任务。因此await做到了不阻塞调用者的执行。
第二个问题,我们通过如下示例进一步说明:
//test_isolate.dart\nvoidtestAwait(){//测试await中断效果\nprint_cjf(&39;);\n\nfun1();\nfun2();//async方法,会await一个异步结果\nfun3();\nfun4();//async方法,但是并没有await异步结果\n\nprint_cjf(&39;);\n}\n\nvoidfun1(){\nprint_cjf(&39;);\n}\n\nvoidfun2()async{//声明异步方法使用async\nprint_cjf(&39;);\nawaitFuture.delayed(Duration(seconds:2));//使用await,会异步等待结果返回\nprint_cjf(&39;);\n}\n\nvoidfun3(){\nprint_cjf(&39;);\n}\n\nvoidfun4()async{//声明异步方法使用async\nprint_cjf(&39;);\nFuture.delayed(Duration(seconds:5));//没有使用await,不会等待结果返回\nprint_cjf(&39;);\n}\n\n//输出log\nI/flutter(24288):16:34:25:354:cjf—testAwaitstart\nI/flutter(24288):16:34:25:354:cjf—fun1\nI/flutter(24288):16:34:25:354:cjf—fun2start//await前的代码被同步执行了\n\nI/flutter(24288):16:34:25:355:cjf—fun3//不需要等待fun2方法执行完,就被同步执行了\nI/flutter(24288):16:34:25:355:cjf—fun4start//被同步执行了\nI/flutter(24288):16:34:25:355:cjf—fun4end//同步执行。并没有在fun4start后等5s再输出。但是在5s内程序不会退出\n\nI/flutter(24288):16:34:25:355:cjf—testAwaitend//fun1,fun2,fun3,fun4并没有阻塞testAwait后续代码的执行\n\nI/flutter(24288):16:34:27:359:cjf—fun2end//fun2start后,等2秒输出end\n\n
这个示例中,我们通过交替调用async和非async函数,来理解同步调用。我们对比观察fun2和fun4中log的输出情况,可以发现:
fun2中,await前的代码&34;在&34;前执行,所以这行代码是被同步执行的;fun2中,awaitFuture.delayed,阻塞了&34;的同步执行,它会等待delay2秒后才被执行,所以最后被执行;fun4中,Future.delayed没有使用await,函数执行没有被阻塞,&34;在&34;前执行,所以是被同步执行的,即Future.delayed创建的异步任务不会打断当前函数执行。
这个示例中,Future.delayed函数本身是同步执行的,它创建一个异步任务,放入当前Isolate的event队列后返回。而awaitFuture.delayed则需要等待创建的异步任务执行完成后,再恢复当前函数后续代码的执行。
通过log分析可知:await关键字,把同一个函数分割为同步执行部分和异步执行两部分await关键字之前的代码和调用者在同一个任务中同步执行。而没有await调用的async函数,代码都是同步执行的。
所以在_DemoPageState例子中,getNetData函数位置3的代码会和调用者在同一个任务中被同步执行。而loadData函数位置2的代码属于待处理任务,会被异步执行。
在4.1章节的testMicroTask微任务例子中,我们也可以看到funDelay异步方法await前的&34;代码是被同步执行的。
对比testFuture例子中Future创建和then回调的例子,从log中也可以知道,Future中创建的异步任务,都是被异步执行的。
await的使用效果和AndroidKotlin协程很类似,launch和async只是启动协程job,jobawait要等待协程结果,才会真正阻塞当前函数后续代码的执行,但是它不阻塞调用线程中其它代码的运行。虽然Kotlin协程的异步原理和Flutter不同,但通过单线程异步支持大量并发操作的设计思想类同,可以一起对比学习。
Future和async,await两种异步方式,在我们的项目中都有频繁使用,各有优点和适用场景,我们可以通过例子可以更直观的说明:
main(){\nnewFuture\n.then(funA())\n.then(funB());//then明确表现出了funB依赖funA\n}\n\nmain()async{\nawaitfunA();\nawaitfunB();//funB依赖funA的关系不明显\n}\n
Future能明确方法执行的依赖次序,其它开发者不容易破坏这层依赖逻辑。而await没有明确体现方法之间的依赖关系。对于没有通过返回值直接体现依赖的情况下,其他开发者不容易理解到这层依赖,会导致后续代码难以维护。
而await的风格,代码更简洁美观,如果没有依赖关系,更推荐这种风格。
4.4IO异步
在前面章节中,testIsolate,testFuture和testAwait例子中,这些函数和新建异步任务都是在RootIsolate中运行的。以及_DemoPageState例子中加载网络数据的getNetData耗时方法也是在RootIsolate中运行的。
但是TaskRunner章节中明确UIRunner负责界面的更新,也运行在RootIsolate中。为了避免界面卡顿,必然不能在RootIsolate的异步任务中执行耗时任务。
那么像网络请求,文件读取,海量计算,图片编解码等耗时函数的调用,是不是都需要创建新的Isolate来处理呢?
我们在实际项目中,会大量使用网络相关耗时IO操作。下面通过网络请求过程为例,进一步分析IO异步:
//home_page.dart\nclassKKHomePageStateextendsBaseThemeState<KKHomePage>withAutomaticKeepAliveClientMixin,TickerProviderStateMixin{\n……….\n\n@override\nvoidinitState(){\nsuper.initState();\n…….\nif(_login){\nprint_cjf(&34;+Isolate.current.debugName.toString());\n\n_refresh();//准备刷新数据\nprint_cjf(&34;+Isolate.current.debugName.toString());\n}\n}\n……\n\nFuture<void>_refresh()async{\nprint_cjf(&34;+Isolate.current.debugName.toString());\n\nawait_viewModel.getList(15,1);//获取网络数据,等待数据结果。虽然没有直接使用返回值,但是此方法后续的代码中依赖网络数据,所以此处必须await。但如果后续代码是通过监听的方式来使用网络数据,此处可去掉await。\n\nprint_cjf(&34;+Isolate.current.debugName.toString());\nhomeAllList.currentState?.resetDataAndRefresh();\n……..\n}\n}\n\n//subscribe_list_vm.dart\nFuture<void>getList(int?limit,int?status)async{\n……..\n\nprint_cjf(&34;+Isolate.current.debugName.toString());\n\nawaitNetManager.getSubjectSubscribeClient()\n.getMySubscribed(BaseRequestParams().makeSignMap(urlMap:requestMap))//获取用户订阅的主题\n.then((value){\nprint_cjf(&34;+Isolate.current.debugName.toString());\n……\n}\n……..\n}\n\n//log输出:\nI/flutter(13129):15:32:23:235:cjf—h02—main//main_page中的initstate,调用_refresh之前。同步执行。\nI/flutter(13129):15:32:23:236:cjf—h5—main//进入_refresh()async{}方法,调用_viewModel.getList之前。同步执行。\nI/flutter(13129):15:32:23:238:cjf—h7—main//同步执行。await_viewModel.getList(15,1);进入到subscribe_list_vm中的getList方法。同步执行。\nI/flutter(13129):15:32:23:239:cjf—h022—main//同步执行。main_page中的initstate,调用_refresh之后。同步执行。\n\nI/flutter(13129):15:32:23:742:cjf—h8—main//awaitNetManager.getSubjectSubscribeClient().getMySubscribed方法返回。异步步执行。\nI/flutter(13129):15:32:23:760:cjf—h6—main//进入_refresh()async{}方法,调用_viewModel.getList之后。异步执行。\n\n
从log可以看出网络请求getListasync方法的代码也是在RootIsolate执行的,并没有启动新的Isolate。
await等待aysnc方法结果时,会产生Isolate内的异步调度,即把待处理任务h6和h8放到当前Isolate的消息队列中,然后继续运行当前任务打印&34;。
那么NetManager.getSubjectSubscribeClient通过retrofit获取网络数据的耗时IO也是在RootIsolate中执行的吗?
我们通过网络限速,使得获取网络数据的接口5s后返回结果。验证发现KKHomePage在这5s中可以流畅地进行操作,且能打印出RootIsolate中其它异步任务执行的log,这说明网络耗时IO没有在RootIsolate中执行,这是什么原因呢?
这是因为网络请求过程中,当运行到系统IO接口时,会将IO任务交给系统处理(属于系统的线程),并将异步任务加入到事件队列,然后RootIsolate回到事件循环,获取事件队列中的下一个任务进行处理。
Flutter文档中,没有明确哪些接口属于系统IO接口,但是通过DartSDK的源码跟踪,我们可以发现,网络请求HttpClient最后的IO操作是在dart虚拟机中的nativesocket中实现的,而socket中使用了线程池来管理线程,并把IOTask分配到各子线程中运行。
所以,对于有系统IO操作的耗时任务,如网络请求,文件读取等,可以使用await,等待耗时IO操作的异步结果。
而执行大量高CPU的运算类耗时任务,如耗时计算,编解码等,即便使用await,但所有代码都运行在RootIsolate中,所以仍然会导致RootIsolate没法及时处理其他异步任务,从而导致UI卡顿。所以官方推荐高CPU耗时任务,应该使用新的Isolate去执行,如前面章节的testCompute例子所示。
通过上面的分析,我们就可以很好地理解,为什么官方只说Isolate不支持高CPU操作,而没有说不支持耗时IO操作了。
开辟新的Isolate的成本比较高,所以对于不太耗时的任务,还是建议用Future或await的方式。那么,什么样的任务算高CPU的耗时任务呢?这个没有绝对标准,建议毫秒级的任务用Future或await,而百毫秒级的任务,比如图片处理,数据加密等,开启新的Isolate。
4.5Js异步
Flutter中的线程异步和浏览器中异步原理是一样的,而且async,await语法糖也是在ES7标准中推出的,通过对比,我们可以进一步对Flutter的异步加深了解。
在JavaScript的世界中,所有代码都是单线程执行的,即通常情况下,Js代码不必考虑多线程。JS中,异步的目的是为了提高CPU的执行效率,提高用户体验。它和同步一样,运行在同一线程中。它和同步的差别在于,同一流水线上各个代码片段的执行顺序不同。
下面我们通过示例来看一下JS中的异步:
//test.js\nasyncfunctionfun1(){\nvarvConsole=newVConsole();\nconsole.log(&39;)\n\nvara=123\nconsole.log(&39;)\n\nvarb=awaitfun2()//等待fun2的异步结果\nconsole.log(&39;)\n\nvarc=b\nconsole.log(&39;)\nreturn1;\n}\n\nasyncfunctionfun2(){\nconsole.log(&39;)\n\nvara=123\nconsole.log(&39;)\n\nvarb=awaitfun3()//等待fun3的异步结果\nconsole.log(&39;)\n\nvarc=b\nconsole.log(&39;)\n}\n\nasyncfunctionfun3(){\nconsole.log(&39;)\n\nvara=123\nconsole.log(&39;)\n\nvarb=awaitmakeRequest()//等待网络请求的异步结果\nconsole.log(&39;)\n\nvarc=b\nconsole.log(&39;)\nreturn3;\n}\n\nfunctionmakeRequest(){\nconsole.log(&39;)\n\nhttpRequest=newXMLHttpRequest();\nif(!httpRequest){\nalert(&39;);\nreturnfalse;\n}\n\nhttpRequest.onreadystatechange=alertContents;//注册异步回调\nhttpRequest.open(&39;,&39;);//耗时IO接口,会切换到引擎新线程\nconsole.log(&39;)\n\nhttpRequest.send();//耗时IO接口,会切换到引擎新线程\nconsole.log(&39;)\nreturn4\n}\n\nfunctionalertContents(){//数据ready回调\nconsole.log(&39;+httpRequest.readyState)\nif(httpRequest.readyState===XMLHttpRequest.DONE){\nconsole.log(&39;)\nif(httpRequest.status===200){\nalert(httpRequest.responseText);\nconsole.log(&39;+httpRequest.responseText)\n}else{\nalert(&39;);\nconsole.log(&39;)\n}\n}\n}\n\nfun1()//调用方法。\n\n//log输出\ncjf—fun1—1\ncjf—fun1—2\ncjf—fun2—1\ncjf—fun2—2\ncjf—fun3—1\ncjf—fun3—2//同步执行\n\ncjf—makeRequest—1//同步执行\ncjf—alertContents—1—1//httpRequest.open会回调alertContents\ncjf—makeRequest—2//异步执行\ncjf—makeRequest—3//异步执行\n\ncjf—fun3—3//异步执行\ncjf—fun3—4//异步执行\ncjf—fun2—3//异步执行\ncjf—fun2—4//异步执行\ncjf—fun1—3//异步执行\ncjf—fun1—4//异步执行\n\ncjf—alertContents—1—4//httpRequest.send会异步回调alertContents\ncjf—alertContents—2//httpRequest.send会异步回调alertContents\ncjf—alertContents—4—aproblemwiththerequest.//httpRequest.send会异步回调alertContents\n\n
从上述例子可以看出,Func1,Func2和Func3中的a赋值操作都是同步执行的,b,c的赋值操作是异步执行的。
在浏览器运行await时,会一层一层运行到游览器引擎新建线程接口(比如Ajax请求接口,XMLHttpRequest.send),调用系统IO接口前的代码片段是被同步执行的,之后的代码片段则需要等到异步结果返回后,才能继续执行。
所以await实际上并不是异步执行的&34;,即await前面的代码同步执行,await后面的代码异步执行。因为await后面的异步函数代码中的部分内容,也可能被同步执行,代码会一直运行到系统IO接口处,才算到了分界线。但是从执行的结果来看,await确实达到了“阻塞等待”的效果。
在使用时,我们并不关心异步函数中的部分代码被同步执行了还是被异步执行了,我们关心的是await的异步数据必须返回,才能再继续执行下面的代码。但对原理的深入理解,可以提高我们对系统设计思想的运用能力。
小结
通过上面的学习,我们对Flutter中的线程模型和单线程异步原理有了更深入的理解,开发时也能更好更安全地运用Flutter的Isolate,Future和await。
我们最后总结下文章的核心内容:
Isolate是Dart平台对线程的实现方案。和普通Thread不同,Isolate拥有独立的内存,由线程和独立内存构成。Isolate线程之间并不存在资源抢夺的问题,所以也不需要线程同步机制;简单耗时任务,可以使用简版Isolate:compute;Isolate通过事件循环和消息队列来实现单线程异步并发,这比多线程异步并发要轻便;可以通过Future进行单线程异步并。Future异步的特点是提供了链式调用,可以解决回调地狱,异常捕获和代码执行依赖等问题;也可以通过async和await实现单线程异步并发,它是同步风格的异步操作,比Future更简洁美观;RootIsolate支持IO耗时操作,但不支持高CPU耗时操作。
作者:狐友陈金凤
来源:微信公众号:搜狐技术产品
出处:https://mp.weixin.qq.com/s/XvVD-yG79x8KX1U5LJgMvg
好了,本文到此结束,如果可以帮助到大家,还望关注本站哦!
