單線程開發異步任務?ACE JS框架是如何實現的
做應用開發時,開發語言的選擇至關重要。例如Java語言的線程特性能夠讓多任務并行,從而充分利用硬件資源開發出高性能的應用。而HarmonyOS 2提供的應用開發語言中,不僅有多線程的Java語言,還有單線程的JS語言。
那JS語言該如何充分利用硬件資源開發出高性能的應用呢?為此,HarmonyOS提出了“ACE JS的單線程異步機制”來解決這一問題。
雖然,JS語言本身是無法實現異步功能,但是ACE JS框架提供了多線程的宿主環境,通過消息通信機制讓JS語言有了異步的屬性,接下來我們來了解下具體的實現原理。
ACE開發框架
使用JS開發HarmonyOS應用,使用的開發框架名為ACE(Ability Cross-Platform Environment),該框架適用于手機、平板、智慧屏、智慧表、車機等設備,具備“一次開發,多端部署”的能力。
ACE框架包括應用層(Application)、前端框架層(Framework)、引擎層(Engine)和平臺適配層(Porting Layer),如下圖所示:
- Application:應用層表示開發者使用JS UI框架開發的FA應用,這里的FA應用特指JS FA應用;
- Framework:前端框架層主要完成前端頁面解析,以及提供MVVM(Model-View-ViewModel)開發模式、頁面路由機制和自定義組件等能力;
- Engine:引擎層主要提供動畫解析、DOM(Document Object Model)樹構建、布局計算、渲染命令構建與繪制、事件管理等能力;
- Porting Layer:適配層主要完成對平臺層進行抽象,提供抽象接口,可以對接到系統平臺。比如:事件對接、渲染管線對接和系統生命周期對接等。
ACE開發框架的線程模型
每個HarmonyOS JS應用,都是通過上圖所示的ACE開發框架進行加載渲染的。ACE開發框架包含了JS線程、UI線程、GPU線程、IO線程,并且在ACE框架外還會存在一類后臺任務線程。其中GPU線程與IO線程為ACE框架內部的專有線程,主要作用于ACE框架初始化與頁面加載渲染的過程;UI線程、JS線程和后臺任務線程會與應用開發代碼相關:
- UI線程:負責應用界面的繪制刷新,與應用的進程號相同,又叫主線程。如果開發JS+JAVA的混合編程,JAVA PA(Particle Ability)的onStart/onConnect等Ability生命周期回調便是運行在主線程,若在這些生命周期回調上執行耗時操作則會導致JS UI的繪制刷新卡住。
- JS線程:應用的JS代碼會被JS引擎解析執行,并運行在JS線程上,而JS又是單線程語言,所以目前我們工程中看到的所有的JS代碼都會執行在這個進程下唯一的JS線程上。
- 后臺任務線程:這里是對ACE框架外部的后臺線程的一個統稱,并不單指一個線程,也并不唯一。后臺任務線程包含了Java PA線程、文件操作API、網絡訪問API內部實現等相關線程。
下面我們結合測試代碼,分析這三個線程的作用和關系。
JS線程與UI線程的關系
為了驗證JS線程與UI線程的關系,我們準備了一個實驗性質的Demo,主要代碼以及運行過程的Log如下:
首先我們在IDE建立一個Empty Ablity(JS)模板的HelloWorld工程,在生命周期、按鈕響應回調方法里增加Log以觀察線程情況。剛創建的app.js中Application生命周期默認已經有Log,無需額外添加。
我們只需要在主界面index.js文件中onInit增加日志:
- console.info('page.default onInit');
然后在index.hml中增加一個button以及會一直進行動畫的progress組件:
- <button id='button1' onclick="onButtonClick">I'm a button</button>
- <progress type="circular"/>
最后在index.js中增加按鈕點擊響應事件以及Log,并且嘗試sleep阻塞js線程:
- function sleep(delay) {
- for (var t = Date.now(); Date.now() - t <= delay; );
- }
- onButtonClick() {
- console.info('onButtonClick begin');
- sleep(1000);
- console.info('onButtonClick end');
- }
將應用運行起來,點擊兩次按鈕,得到如下Log:
從輸出的Log中,我們可以看到這個JS FA進程號為22592,也就是說UI線程是22592;生命周期回調以及按鈕響應均在24077線程,這個就是JS線程,所以JS線程與UI線程不是同一個線程。
并且我們嘗試通過sleep方法阻塞JS線程,想觀察JS線程阻塞是否會影響到UI線程的刷新。最終得出的結論是無論JS線程sleep多長時間,UI界面上的progress組件動畫一直會不斷刷新,按鈕也會有按壓效果變化,所以我們可以推測JS線程與UI線程的相互調用應該是通過某種消息機制完成的,而不是阻塞式的調用。
JS線程與后臺任務線程的關系
ACE JS框架提供了JS FA(Feature Ability)調用Java PA(Particle Ability)的機制,該機制提供了一種通道來傳遞方法調用、處理數據返回以及訂閱事件上報。通過以下Demo可以驗證 JS 線程與Java PA線程的關系:
在JS中,我們通過FeatureAbility.callAbility拉起并調用了名為一個類名為ServiceAbility的Java PA,并拿到返回結果:
- var action = {};
- action.bundleName = 'com.blancwu.test';
- action.abilityName = 'com.blancwu.test.ServiceAbility';
- action.messageCode = 1001;
- action.abilityType = 0;
- action.syncOption = 0;
- console.info('FeatureAbility.callAbility begin' + JSON.stringify(action));FeatureAbility.callAbility(action).then(function (value) {
- console.info('FeatureAbility.callAbility async result ' + JSON.stringify(value));
- })
- console.info('FeatureAbility.callAbility end' + JSON.stringify(action));
在ServiceAbility的onRemoteRequest中增加Log輸出,并sleep 1秒鐘,以便觀察線程情況與之間關系:
- @Override
- public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) throws RemoteException {
- HiLog.info(LABEL_LOG, "onRemoteRequest begin " + code);
- if (code == 1001) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- Map<String, Object> result = new HashMap<String, Object>();
- result.put("result", 1);
- reply.writeString(ZSONObject.toZSONString(result));
- }
- HiLog.info(LABEL_LOG, "onRemoteRequest end " + code);
- return super.onRemoteRequest(code, data, reply, option);
- }
完成以上代碼后,并進行執行,可得到的Log如下:
我們觀察到本次運行主進程(UI線程)號為4133,JS代碼執行在JS線程5887,Java PA響應onRemoteRequest執行在另一個后臺任務線程5837。通過Log我們看到onRemoteRequst即使阻塞了后臺任務線程1s也不會影響JS線程的并行執行以及主線程(UI線程)上動畫的刷新,做到了JS線程與后臺任務線程異步地執行事務。
JS線程的異步機制
上面從代碼實驗角度觀察到了JS線程與其他線程的異步關系,那么JS線程處理來自其他多個線程的調用是怎么實現的呢?
首先,我們來看一下傳統瀏覽器環境下的運行機制:
上圖中,JS線程中的函數調用會存在于棧(stack)中,棧中的函數可以調用瀏覽器環境提供的WebAPIs,包含了DOM、ajax、timeout等API,這些API會在瀏覽器環境提供的另外一個外部線程執行,執行完成后會在任務隊列(callback queue)中加入對應的回調事件(如onClick、onLoad、onDone)。
當棧中的代碼執行完畢,即棧清空后,JS線程又會通過event loop取出任務隊列中的下一個任務進行執行,以此類推完成整個程序執行。
HarmonyOS ACE開發框架同樣遵循上述最基本的EventLoop調度機制,并且提供了更多的機制和API,讓業務邏輯可以在外部線程執行,包含了上面提到的Java PA以及異步回調的系統能力API。其中,異步回調的系統能力API包含如文件系統操作和網絡操作等,具體大家可以按照我們實驗Demo的方法去嘗試一下。
● 參考 https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-apis-file-storage-0000000000629445
未來發展的展望
目前ACE JS應用內實現多線程的最佳方式是通過混合編程調用Java PA方式,未來,隨著純JS應用越來越多,只支持單線程的JS ACE框架的異步API并不能解決各種復雜場景問題。
單線程的JS加上異步API能夠很好解決單個I/O阻塞的問題,但是如果遇到大量的I/O事件,比如批刪除大量文件,通過for循環發起了大量異步任務,也會降低執行效率,甚至阻塞其他異步任務的執行。并且如果要使用JS語言開發計算密集型的任務,也無法在唯一的JS線程上進行。
這時就需要一個真正的JS多線程處理機制了,雖然目前HarmonyOS 2還未支持,但未來HarmonyOS會考慮規劃出與HTML5類似提供支持WebWorker機制,支持開發出多線程的JS代碼,提供給應用開發者更多的發揮空間。