網站首頁 編程語言 正文
1. 認識Zone
Zone像一個沙盒,是我們代碼執行的一個環境。
我們的main
函數默認就運行在Root Zone
當中。
子Zone的構造有點像Linux中的進程,它支持從當前的Zone中Fork出一個子Zone:
Zone myZone = Zone.current.fork(...)
對于Zone而言,它有兩個構造函數:
- ZoneSpecification
- ZoneValues
ZoneSpecification:其實是Zone內部代碼行為的一個提取,我們可以通過它來為Zone設置一些監聽。
ZoneValues:Zone的變量,私有變量。
類似Linux 通過Fork創建的 myZone默認也具有源Zone的ZoneSpecification和ZoneValues。
1.1 ZoneValues
和Linux類似地,當Zone做Fork的時候,會將父Zone所持有的ZoneSpecification、ZoneValues會繼承下來,可以直接使用。并且是支持追加的,secondZone在firstZone的基礎之上,又追加了extra_values
屬性,不會因為secondZone的ZoneValues就導致name屬性被替換掉。
Zone firstZone = Zone.current .fork(specification: zoneSpecification, zoneValues: {"name": "bob"}); Zone secondZone = firstZone.fork(zoneValues: {"extra_values": 12345}); secondZone.run(() { print(secondZone["name"]); // bob print(secondZone["extra_values"]); // 12345 }
我們可以使用Zone.current
,訪問當前的代碼執行在哪一個Zone
當中,默認情況下,代碼執行在Root Zone
當中,后續會根據需求分化出多個Zone
,也可以使用Zone.root
訪問到RootZone的實例。
1.2 ZoneSpecification
和ZoneValues不同,ZoneValues支持追加不同的屬性,而ZoneSpecification
只支持重寫,并且RootZone
已經預設好了一系列的Zone中運行的規則,一旦我們重寫了ZoneSpecification
的一些方法回調,之前的一些功能可能會消失。
這種基于配置對象
的擴展方法和基于繼承
的子類的重寫是不一樣的,該方法具有更強的擴展性,但是在類似于特性保留的機制上就明顯不如繼承來的方便,一旦重寫某個方法,該方法原有的特性需要重新實現一遍,否則原有的功能會消失。
如果你只重寫了其中的一個方法,那么其他方法不會被覆蓋,依然采用默認配置。
ZoneSpecification的構造方法中,包含非常多的參數,其中絕大多數都是以回Callback形式出現,首先來看看run
系列的方法:
RunHandler? run, RunUnaryHandler? runUnary, RunBinaryHandler? runBinary,
其實這三個方法的區別在于參數,我們看看RunHandler
、RunUnaryHandler
和RunBinaryHandler
的具體定義:
typedef RunHandler = R Function<R>( Zone self, ZoneDelegate parent, Zone zone, R Function() f);? typedef RunUnaryHandler = R Function<R, T>( Zone self, ZoneDelegate parent, Zone zone, R Function(T arg) f, T arg);? typedef RunBinaryHandler = R Function<R, T1, T2>(Zone self, ZoneDelegate parent, Zone zone, R Function(T1 arg1, T2 arg2) f, T1 arg1, T2 arg2);
不難發現,三者除了固定的:self
、parent
、zone
之外,區別就在于
UnaryHandler
和BinaryHandler
提供了分別提供了一個參數、兩個參數的選項。這個參數的作用是提供給另外一個參數:f,類型是一個Function
,顯然它是我們調用Zone.run
方法傳進來的body
參數,以RunHandler
為例,我們對run
做出如下的定義:
Zone secondZone = firstZone.fork( zoneValues: {"extra_values": 12345}, specification: ZoneSpecification( run: <int>(self, parent, zone, f) { int output = f(); return output; }, ));
我們在外部調用secondZone.run(()=>...)
時,就可以在run方法的開始、結尾做一些其他的事情了:
secondZone.run(body);// 執行 run: <int>(self, parent, zone, f) { // 1. print("before"); int output = f();// 這里的f就是body,它是可執行的 print("after"); return output; // 2. },
直覺告訴我,1/2之間的代碼應該是在Second Zone
中執行的,但是打印一下Zone.root
,我們發現實際上是在Root Zone
中執行的,二者的HashCode相同。
// 在body內部打印的 body internal Zone:195048515 // Root Zone:195048515 // first Zone:700091970 second Zone:707932504
大致上去跟了一下代碼,發現默認的run方法的實現,被我們新編寫的run
參數覆蓋掉了,所以會導致本該在secondZone中執行的body結果在Root Zone
中執行。然后再run
參數的注釋里,發現了這么一段話:
Since the root zone is the only zone that can modify the value of [current], custom zones intercepting run should always delegate to their parent zone. They may take actions before and after the call.
大致上的意思是:
因為Root Zone是唯一能夠修改Zone.current
參數的Zone,所以自定義的Zone攔截run方法必須總是將方法交給它們的父Zone去代為處理。而run自己可以在run調用之前或者之后采取一些行動。
也就是說,我們不能直接return f();
,而要把f()
委托給parent
來執行,像這樣:
secondZone.run(body);// 執行 ? run: <int>(self, parent, zone, f) { // 1.這里執行在Root Zone print("before"); Function output = parent.run(self, () { // 這里執行在second Zone return f(); }); print("after"); return output; // 2. },
委托之后,由Root Zone
去做統一的調度、Zone的切換。這樣,我們再去打印一下執行的Zone,發現正常了,secondZone.run
方法(其實是被ZoneSpecification中的run指定的方法)的Zone仍然是Root Zone
,而我們傳遞過去的任務被執行在了self
之中,也就是SecondZone
當中,符合我們的預期:
current zone:692810917
body internal Zone:558922284
Root Zone:692810917
firstZone Zone:380051056
second Zone:558922284
額外地,可以牽出ZoneDelegate
是做什么的,它允許子Zone,訪問父Zone的一些方法,與此同時保留自己額外的一些行為:綠框表示額外的行為,當Zone A
調用Zone B
的run時,它通常執行在調用者的Zone當中,也就是ZoneA。
1.3 通過runZoned快速創建Zone
Dart提供了runZoned方法,支持Zone的快速創建:
R runZoned<R>(R body(), {Map<Object?, Object?>? zoneValues, ZoneSpecification? zoneSpecification, @Deprecated("Use runZonedGuarded instead") Function? onError}) {
其中body、zoneValues、zoneSpecification都是老熟人了,關鍵在于它對于run方法的處理:
/// Runs [body] in a new zone based on [zoneValues] and [specification]. R _runZoned<R>(R body(), Map<Object?, Object?>? zoneValues, ZoneSpecification? specification) => Zone.current .fork(specification: specification, zoneValues: zoneValues) .run<R>(body);
如果我們不顯式地傳遞一個ZoneSpecififation
進來,fork
時傳進去的是null,自然不會導致Specification被我們重寫,因此代碼能按照Dart默認的實現方式,運行在一個新的、Fork出來的Zone當中(至少能看出不是Root Zone):
runZoned(() { print("body internal Zone:" + Zone.current.hashCode.toString()); print("Root Zone:" + Zone.root.hashCode.toString()); }); ? // 打印結果 body internal Zone:253994638 Root Zone:1004225004
但是如果你像之前手動fork一樣,指定它的ZoneSpecification,又不把f委托給上層Zone處理,那么就會:
body internal Zone:44766141 Root Zone:44766141
2. 異步基本原理和異常捕獲
默認大家已經知道什么事單線程模型,以及Future的執行機制了,Dart的單線程模型和事件循環機制。
來看看這段簡單的代碼:
void asyncFunction() { print('1'); Future((){ print('2'); }).then((e) { print('3'); }); print('4'); }
大家都知道,這段代碼的輸出的順序是:1423,它的大致流程是:
print 1 創建一個Future,并扔到Event Queue末尾 print 4 // 從Event Queue中取出,并執行下一個消息...... 執行Future構造函數中的方法:-> print 2 print 2執行完成,即Future完成,回調它的then: -> print 3
我們為他加上await和async,并稍作改造,寫成async、await的同步形式,同時刪掉4
void asyncFunction() async { print('1'); await Future(() { print('2'); }); print('3'); print('4'); }
它的輸出是:1234,他所做的是:
print 1; 創建一個Future@1,并扔到Event Queue末尾; // 從Event Queue中取出,并執行下一個消息...... 取出Future@1,立刻執行它構造中的方法: -> print 2; 并將之后的代碼打包,重新放到Event Queue的末尾(這里一般會等待IO完成,之后就會去執行和這個回調) 執行完成之后,執行之后的代碼: print 3; print 4;
今天我們不是討論Async和Await的,就不再展開。
但是大家可以比較一下這兩次調用,發現第二種和第一種相比,第二種調用的代碼是會 “回來” 繼續執行的,而第一種的Future創建不搭配await/async的就好比脫韁的野馬,這種代碼我們并不關心它的結果,自然也不要求代碼在此await,執行起來就無法控制,但在Dart中我們也無法通過try/catch
捕獲異常。
關鍵點在于:async + await是會回到異步阻塞的代碼處(await處)執行的。既然回來了,那么try/catch
自然而然是能夠繼續監聽是否有異常拋出的。
而第一種的Future,即使我們在外面包裹上了try/catch
,而Future的代碼卻是在未來的某個時間內,在Event Queue的末尾的某個位置解包執行的,上下文和try/catch
所在的代碼并沒什么關聯,自然不能攔截到異常。我們可以從Stack Trace中看看這兩種代碼拋出異常時的執行棧:
左側是一種方法的執行棧,throwExceptionFunction()
項相關的棧幀已經消失了,異常自然沒有辦法通過throwExceptionFunction()
中的try/catch
進行捕獲。
問題就出在這了, 對于這種錯誤我們是否有辦法去捕獲呢?
答案仍然還是是今天的主題 : Zone。
3. HandleUncaughtErrorHandler
雖然異步代碼的執行,可能會橫跨多個Event,讓代碼前后的上下文失去聯系,導致異常無法被正常捕獲,但是它仍然在一個Zone之內。
就像仙劍奇俠傳三中,李逍遙對景天說“邪劍仙(Exception)雖身處六界(Event)之外卻是在道(Zone)之內”。
Zone提供了一些特殊的編程接口,讓我們能夠對當前這個Zone沙盒內的未捕獲的異常進行集中處理。
它就是HandleUncaughtErrorHandler
。作為ZoneSpecification
的一個參數,它支持將Zone當中未被處理的錯誤統一歸到這里進行處理(Dart和Java不一樣,Dart的異常本身通常不會導致程序的退出),因此,常使用HandleUncaughtErrorHandler來做異常的統計、上報等等。
另外,因為Dart執行環境的單線程 + 事件隊列機制本身,Dart的try/catch
對于異步代碼是無法處理的,如下的代碼異常會穿透(或者說根本不經過)try/catch后拋出,會在控制臺中留下紅色的報錯。
// Zone.run(()=>throwExceptionFunctino()); void throwExceptionFunction() { try { Future.delayed(const Duration(seconds: 1)) .then((e) => throw("This is an Exception")); } catch (e) { print("an Exception has been Captured: ${e.toString()}"); } }
顯然,異步的異常并沒有被捕獲:
Unhandled exception: This is an Exception #0 throwExceptionFunction.<anonymous closure> (file:///Users/rEd/IdeaProjects/dartProjs/zone/bin/zone.dart:140:22) #1 _rootRunUnary (dart:async/zone.dart:1434:47) #2 _CustomZone.runUnary (dart:async/zone.dart:1335:19) <asynchronous suspension>
但是我們改成這樣呢?
void throwExceptionFunction() async{ try { await Future.delayed(const Duration(seconds: 1)) .then((e) => throw ("This is an Exception")); } catch (e) { print("an Exception has been Captured: ${e.toString()}"); } }
我們對異步的方法throwExceptionFunction()
加了await/async
關鍵字。我們會發現異常,又能被捕獲了:
an Exception has been Captured: This is an Exception Process finished with exit code 0
其實上文已經提到了是異步時Dart代碼上下文切換的原因,這里也不做過多的贅述了,我們像這樣,將我們的App包裹在一個額外的Zone里面,并在它的HandleUncaughtErrorHandler
相關方法做如下定義:
void main() { runZoned(() => runApp(const MyExceptionApp()), zoneSpecification: ZoneSpecification( // print: (self, parent, zone, line) {}, handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, Object error, StackTrace stackTrace) { // 同樣將print代理給上層Zone,這樣就可以在上層捕獲到這些異常了。 parent.print(self," ### \n $stackTrace \n ### "); })); }
隨便找個地方拋出個異常:
floatingActionButton: FloatingActionButton( onPressed: () => Future((){ throw ("ERROR!"); }), ),
我們可以發現,異常在此處被HandleUncaughtErrorHandler
集中捕獲了。
或者我們也可以使用runZoned
自帶的回調來處理,而不是去自己重寫ZoneSpecification
:
// runZonedGuarded替換runZoned runZonedGuarded(() => runApp(const MyExceptionApp()), (Object error, StackTrace stack) { print('stack: $stack'); });
不過,我們去它內部看看,其實它還是HandleUncaughtErrorHandler
實現的。
注意:如果重寫了ZoneSpecification的run相關的方法,可能會導致當前的Zone無法捕獲到異常,就像1.中所說的那樣,基于配置類的重寫將原有特性覆蓋掉了,導致當前代碼并不一定在我們直覺認為的Zone中執行。
這需要編寫者自己去解決這個問題,所以,如果沒有特殊的需求,一般不給Zone傳遞ZoneSpecification選項,如果要傳遞,需要去實現它,以保證相關的功能特性可用。
原文鏈接:https://juejin.cn/post/7121632521917333540
相關推薦
- 2023-01-17 C#實現自定義ListBox背景的示例詳解_C#教程
- 2022-11-15 Python正則表達式re.search()用法詳解_python
- 2022-09-20 C#先判斷是否存在再創建文件夾或文件與遞歸計算文件夾大小_C#教程
- 2022-11-10 Linux實現壓縮文件的生成與查看的常用命令總結_linux shell
- 2022-04-07 Swift實現簡易計算器功能_Swift
- 2022-10-04 Python的getattr函數方法學習使用示例_python
- 2022-10-14 scikit-learn工具包中分類模型predict_proba、predict、decision
- 2022-05-11 Restful的Get請求參數為List
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支