Dart & Flutter 開發技巧 8-14
935
2025-03-31
本文目錄
前言
isolate
event loop
線程模型與isolate
創建單獨的isolate
Stream事件流
前言
說到網絡,就一定會提到異步編程。對于涉及網絡的操作,在客戶端的開發中都是通過異步實現的。在flutter里,異步是用Future來修飾的,并且運行在event loop里。
flutter的異步特性和Android的Looper以及前端的event loop有點像,都是不斷地從事件隊列里獲取事件然后運行,并通過異步操作有效防止一些耗時任務對主UI線程地影響。
isolate
Flutter中很重要的一個概念就是isolate,它是通過Flutter Engine層面的一個線程來實現的,而實現isolate的線程又是由Flutter管理和創建的。除了isolate所在的線程以外,還有其他的線程,它們跟Flutter的線程模型(Threading Mode)有關。在我們介紹完isolate的基本用法和概念之后,后面會專門由一節介紹Flutter Engine層線程模型相關知識。
所有的Dart代碼都是在isolate上運行的。通常情況下,我們的應用都是運作在main isolate中的,在必要時我們可以通過相關的API創建新的isolate,以便更好的利用CPU的特性,并以此提高效率。需要注意一點,多個isolate無法共享內存,必須通過相關的API通信才可以。
event loop
另一個比較重要的概念是event loop。學過過JS前端知識的一定對event loop有所了解,理解它并不困難,而且Flutter簡單一些,下面是它的原理圖:
(1)運行App并執行main方法
(2)開始并優先處理microtask queue,直到隊列為空。
(3)當microtask queue為空后,開始處理event queue。如果event queue里面有event,則執行,每執行一條在判斷此時新的microtask queue是否為空,并且每一次只取出一個來執行??梢赃@樣理解,在處理所有event之前我們會做一些事情,并且會把這些事情放在microtask queue中。
(4)microtask queue和event queue都為空,則App可以正常退出。
特別注意:當處理microtask queue時,event queue是會被阻塞的。所以microtask queue中應避免任務太多或長時間處理,否則將導致App的繪制和交互等行為被卡住。所以,繪制和交互等應該作為event存放在event queue中,這樣更合適。
我們直接來看一個例子,代碼如下:
_loopDemo(){ print("開始"); scheduleMicrotask(()=>print("microtask隊列1")); new Future.delayed(new Duration(seconds: 1),()=>print("future隊列1 delayed")); new Future(()=>print("future隊列2")) .then((_)=>print("future隊列2 then1")) .then((_){ print("future隊列2 then 2"); scheduleMicrotask(()=>print("future隊列2 then2中microtask隊列")); }).then((_)=>print("future隊列2 then3")) .then((_)=>print("future隊列2 then4")); scheduleMicrotask(()=>print("microtask隊列2")); new Future(()=>print("future隊列3")) .then((_)=>new Future(()=>print("future隊列3 then1 future"))) .then((_)=>print("future隊列3 then2")) .then((_)=>print("future隊列3 then3")); new Future(()=>print("future隊列4")) .then((_){ new Future(()=>print("future隊列4 then1 future")); }).then((_)=>print("future隊列4 then2")) .then((_)=>print("future隊列4 then3")); scheduleMicrotask(()=>print("microtask隊列3")); print("結束");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
這里,scheduleMicrotask代表流程圖中的microtask queue,future代表event queue,控制臺打印的結果如下:
這段代碼有幾點需要注意:
(1)Future.delayed表示延遲執行,在設置的延遲時間到了之后,才會被放在event loop隊列尾部。
(2)Future.then里的任務不會添加到event queue中,要保證異步任務的執行順序就一定要用then。
線程模型與isolate
上面已經介紹,isolate是通過Flutter Engine層面的一個線程來實現的,而實現isolate的線程是由Flutter管理和創建的。其實,Flutter Engine線程的創建和管理是由embedder(嵌入層)負責的,embeder是平臺引擎移植的中間代碼。下面就是Flutter Engine的運行架構:
可以看到它給我們提供了4種task Runner:
(1)Platform Task Runner
Platform Task Runner是Flutter Engine的主Task Runner,它不僅可以處理與Engine的交互,還可以處理來自Native(Android/IOS)平臺的交互。平臺的API都只能在主線程中被調用。每一個Flutter應用啟動的時間都會創建一個Engine實例,Engine創建的時候都會創建一個Platform Thread 供Platform Task Runner使用。即使Platform Thread被阻塞,也不會直接導致Flutter應用的卡頓。盡管如此,也不建議在這個Runner里執行如此繁重的操作,長時間卡住Platform Thread,應用有可能會被系統的Watchdog強行終止。
(2)UI Task Runner
UI Task Runner不能想當然的理解為像Android那樣是運行在主線程的,它其實是運行在線程對應到平臺的線程上的,屬于子線程。Root isolate在引擎啟動時綁定了不少Flutter所需要的方法,從而使其具備調度/提交/渲染幀的能力。在Root isolate通知Flutter Engine有幀需要被渲染后,渲染時就會生成Layer Tree并交給Flutter Engine。此時,僅生成了需要描繪的內容,然后才創建和更新Layer Tree,該Tree最終決定什么內容會在屏幕上被繪制,因此 UI Task Runner如果過載會導致卡頓。
isolate可以理解為單線程,如果運算量大,可以考慮采用獨立的isolate,單獨創建的isolate(非Root isolate)不支持綁定Flutter的功能,也不能調用,只能做數據運算。單獨創建的isolate的生命周期會受Root isolate控制,只要 Root isolate停止了,那么其他的isolate也會停止。isolate運行的線程是Dart VM里的線程池提供的。
UI Task Runner還可以處理來自Native Plugins的消息,timers,microtask,異步I/O操作。
(3)GPU Task Runner
GPU Task Runner被用于執行與設備GPU相關的調用,它可以將GPU Task Runner生成的Layer Tree所提供的信息轉換為實際的GPU指令。GPU Task Runner的運行線程對應著平臺的子線程。UI Task Runner和GPU Task Runner在不同的線程上運行。GPU Runner會根據目前幀被執行的進度向UI Task Runner要求下一幀的數據。在任務繁重的時候,UI Task Runner會延遲任務進程。這種調度機制確保了GPU Task Runner不至于出現過載現象,同時避免了UI Task Runner不必要的消耗。如果在線程中耗時太久的話,就會造成Flutter應用的卡頓,因此在GPU Task Runner中,盡量不要做耗時的任務。例如,加載圖片的同時讀取圖片的數據,就不應該在GPU Task Runner中運行,而應該放在IO Task Runner中進行。
(4)IO Task Runner
IO Task Runner的運行線程也對應這平臺的子線程。當UI和GPU Task Runner都出現了過載的情況時,會導致Flutter應用卡頓。IO Task Runner就會負責做一些預先處理的讀取操作,然后再上報給GPU Task Runner,且只有GPU Task Runner可以訪問到GPU。簡單點說就是,IO Task Runner是GPU Task Runner的助手,可以減少GPU Task Runner額外的操作。IO Task Runner并不會阻塞Flutter。雖然在通過IO Task Runner加載圖片和資源的時候可能會有延遲,但是還是建議為IO Task Runner單獨建立一個線程。
創建單獨的isolate
在UI Task Runner過載的情況下,可以創建單獨的isolate。單獨創建的isolate之間沒有共享內存,所以它們之間的唯一通信方式只能通過Port進行,而且Dart中的消息傳遞總是異步的。isolate與線程有著本質的區別,操作系統內的線程之間是可以共享內存的,而isolate不會,這是最為關鍵的區別。那么如何創建isolate呢,代碼如下:
_isolateDemo() async{ //isolate所需要的參數必須要有SendPort,SendPort有必須要要有ReceivePort來創建 final receivePort=new ReceivePort(); //創建新的isolate,這里使用Isolatel.spawn函數創建 await Isolate.spawn(_isolate, receivePort.sendPort); //發送消息 var sendPort=await receivePort.first; var msg=await sendReceive(sendPort, "fpp"); print("received $msg"); msg=await sendReceive(sendPort, "bar"); print("received $msg"); } //新isolate的入口函數 _isolate(SendPort replyTo) async{ //實例話一個receivePort用于接收消息 var port=new ReceivePort(); //把它的sendPort發送給宿主isolate,以便宿主可以給它發消息 replyTo.send(port.sendPort); //監聽消息,從Port里獲取 await for(var msg in port){ var data=msg[0]; SendPort replyTo=msg[1]; replyTo.send("接受到了:"+data); if(data=="bar")port.close(); } } //對某一個Port發送消息,并接受結果 Future sendReceive(SendPort port,msg){ ReceivePort response=new ReceivePort(); port.send([msg,response.sendPort]); return response.first; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
如果你需要處理過度耗時的任務就可以使用如上方式進行創建isolate。
Stream事件流
Stream是與Flutter相關的一個重要概念,它首先是基于事件流來驅動并設計代碼的,然后監聽和訂閱相關事件,并且對事件的變化進行處理和響應。Stream不是Flutter中特有的,而是Dart語言中自帶的。我們可以把Stream想象成管道(pipe)的兩端,它只允許從一端插入數據并通過管道從另外一端流出數據。我們可以通過StreamController來控制Stream(事件源),比如StreamController提供了類型為StreamSink,屬性為Sink的控制器作為入口。
Stream可以傳輸什么?它支持任何類型數據的傳輸,包括基本值,事件,對象,集合等,即任何可能改變的數據都可以被Stream傳毒和觸發。當我們在傳輸數據時,可以通過listen函數監聽來自StreamController的屬性,在監聽之后,可以通過StreamSubscription訂閱對象并接受Stream發送數據變更的通知。
Stream也是異步處理的核心API,那么,它與同為異步處理的Future有何區別呢?答案時Future表示“將來”一次異步獲取得到的數據,而Stream是多次異步獲取得到的數據;Future將返回一個值,而Stream將返回多次值。在講Future時我們提到了FutureBuilder類,對于Stream,它有StreamBuilder類負責監聽Stream。當Stream數據流出時會自動重新構建組件,并通過Builder進行回調。
下面就是Stream的簡單例子,代碼如下:
class _MyHomePageState extends State
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
上面就是要給簡單的計數程序,不過這里是通過stream實現的,上面沒什么難點,都是常用到的一些代碼,這里就不做過多的贅述了。
Android API Flutter 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。