自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

React中的任務(wù)饑餓行為

開發(fā) 前端
本文是在React中的高優(yōu)先級(jí)任務(wù)插隊(duì)機(jī)制基礎(chǔ)上的后續(xù)延伸,先通過閱讀這篇文章了解任務(wù)調(diào)度執(zhí)行的整體流程,有助于更快地理解本文所講的內(nèi)容。

本文是在React中的高優(yōu)先級(jí)任務(wù)插隊(duì)機(jī)制基礎(chǔ)上的后續(xù)延伸,先通過閱讀這篇文章了解任務(wù)調(diào)度執(zhí)行的整體流程,有助于更快地理解本文所講的內(nèi)容。

饑餓問題說到底就是高優(yōu)先級(jí)任務(wù)不能毫無底線地打斷低優(yōu)先級(jí)任務(wù),一旦低優(yōu)先級(jí)任務(wù)過期了,那么他就會(huì)被提升到同步優(yōu)先級(jí)去立即執(zhí)行。如下面的例子:

我點(diǎn)擊左面的開始按鈕,開始渲染大量DOM節(jié)點(diǎn),完成一次正常的高優(yōu)先級(jí)插隊(duì)任務(wù):

而一旦左側(cè)更新的時(shí)候去拖動(dòng)右側(cè)的元素,并在拖動(dòng)事件中調(diào)用setState記錄坐標(biāo),介入更高優(yōu)先級(jí)的任務(wù),這個(gè)時(shí)候,左側(cè)的DOM更新過程會(huì)被暫停,不過當(dāng)我拖動(dòng)到一定時(shí)間的時(shí)候,左側(cè)的任務(wù)過期了,那它就會(huì)提升到同步優(yōu)先級(jí)去立即調(diào)度,完成DOM的更新(低優(yōu)先級(jí)任務(wù)的lane優(yōu)先級(jí)并沒有變,只是任務(wù)優(yōu)先級(jí)提高了)。

要做到這樣,React就必須用一個(gè)數(shù)據(jù)結(jié)構(gòu)去存儲(chǔ)pendingLanes中有效的lane它對(duì)應(yīng)的過期時(shí)間。另外,還要不斷地檢查這個(gè)lane是否過期。

這就涉及到了任務(wù)過期時(shí)間的記錄 以及 過期任務(wù)的檢查。

lane模型過期時(shí)間的數(shù)據(jù)結(jié)構(gòu)

完整的pendingLanes有31個(gè)二進(jìn)制位,為了方便舉例,我們縮減位數(shù),但道理一樣。

例如現(xiàn)在有一個(gè)lanes:

  1. 0 b 0 0 1 1 0 0 0 

那么它對(duì)應(yīng)的過期時(shí)間的數(shù)據(jù)結(jié)構(gòu)就是這樣一個(gè)數(shù)組:

  1. [ -1, -1, 4395.2254, 3586.2245, -1, -1, -1 ] 

在React過期時(shí)間的機(jī)制中,-1 為 NoTimestamp

即pendingLanes中每一個(gè)1的位對(duì)應(yīng)過期時(shí)間數(shù)組中一個(gè)有意義的時(shí)間,過期時(shí)間數(shù)組會(huì)被存到root.expirationTimes字段。這個(gè)計(jì)算和存取以及判斷是否過期的邏輯

是在markStarvedLanesAsExpired函數(shù)中,每次有任務(wù)要被調(diào)度的時(shí)候都會(huì)調(diào)用一次。

記錄并檢查任務(wù)過期時(shí)間

在React中的高優(yōu)先級(jí)任務(wù)插隊(duì)機(jī)制那篇文章中提到過,ensureRootIsScheduled函數(shù)作為統(tǒng)一協(xié)調(diào)任務(wù)調(diào)度的角色,它會(huì)調(diào)用markStarvedLanesAsExpired函數(shù),目的是把當(dāng)前進(jìn)來的這個(gè)任務(wù)的過期時(shí)間記錄到root.expirationTimes,并檢查這個(gè)任務(wù)是否已經(jīng)過期,若過期則將它的lane放到root.expiredLanes中。

 

  1. function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { 
  2.   // 獲取舊任務(wù) 
  3.   const existingCallbackNode = root.callbackNode; 
  4.  
  5.   // 記錄任務(wù)的過期時(shí)間,檢查是否有過期任務(wù),有則立即將它放到root.expiredLanes, 
  6.   // 便于接下來將這個(gè)任務(wù)以同步模式立即調(diào)度 
  7.   markStarvedLanesAsExpired(root, currentTime); 
  8.  
  9.   ... 
  10.  

markStarvedLanesAsExpired函數(shù)的實(shí)現(xiàn)如下:

暫時(shí)不需要關(guān)注suspendedLanes和pingedLanes

 

  1. export function markStarvedLanesAsExpired( 
  2.   root: FiberRoot, 
  3.   currentTime: number, 
  4. ): void { 
  5.   // 獲取root.pendingLanes 
  6.   const pendingLanes = root.pendingLanes; 
  7.   // suspense相關(guān) 
  8.   const suspendedLanes = root.suspendedLanes; 
  9.   // suspense的任務(wù)被恢復(fù)的lanes 
  10.   const pingedLanes = root.pingedLanes; 
  11.  
  12.   // 獲取root上已有的過期時(shí)間 
  13.   const expirationTimes = root.expirationTimes; 
  14.  
  15.   // 遍歷待處理的lanes,檢查是否到了過期時(shí)間,如果過期, 
  16.   // 這個(gè)更新被視為饑餓狀態(tài),并把它的lane放到expiredLanes 
  17.  
  18.   let lanes = pendingLanes; 
  19.   while (lanes > 0) { 
  20.  
  21.     /* 
  22.      pickArbitraryLaneIndex是找到lanes中最靠左的那個(gè)1在lanes中的index 
  23.      也就是獲取到當(dāng)前這個(gè)lane在expirationTimes中對(duì)應(yīng)的index 
  24.      比如 0b0010,得出的index就是2,就可以去expirationTimes中獲取index為2 
  25.      位置上的過期時(shí)間 
  26.     */ 
  27.  
  28.     const index = pickArbitraryLaneIndex(lanes); 
  29.     const lane = 1 << index
  30.     // 上邊兩行的計(jì)算過程舉例如下: 
  31.     //   lanes = 0b0000000000000000000000000011100 
  32.     //   index = 4 
  33.  
  34.     //       1 = 0b0000000000000000000000000000001 
  35.     //  1 << 4 = 0b0000000000000000000000000001000 
  36.  
  37.     //    lane = 0b0000000000000000000000000001000 
  38.  
  39.     const expirationTime = expirationTimes[index]; 
  40.     if (expirationTime === NoTimestamp) { 
  41.       // Found a pending lane with no expiration time. If it's not suspended, or 
  42.       // if it's pinged, assume it's CPU-bound. Compute a new expiration time 
  43.       // using the current time
  44.       // 發(fā)現(xiàn)一個(gè)沒有過期時(shí)間并且待處理的lane,如果它沒被掛起, 
  45.       // 或者被觸發(fā)了,那么去計(jì)算過期時(shí)間 
  46.       if ( 
  47.         (lane & suspendedLanes) === NoLanes || 
  48.         (lane & pingedLanes) !== NoLanes 
  49.       ) { 
  50.  
  51.         expirationTimes[index] = computeExpirationTime(lane, currentTime); 
  52.       } 
  53.     } else if (expirationTime <= currentTime) { 
  54.       // This lane expired 
  55.       // 已經(jīng)過期,將lane并入到expiredLanes中,實(shí)現(xiàn)了將lanes標(biāo)記為過期 
  56.       root.expiredLanes |= lane; 
  57.     } 
  58.     // 將lane從lanes中刪除,每循環(huán)一次刪除一個(gè),直到lanes清空成0,結(jié)束循環(huán) 
  59.     lanes &= ~lane; 
  60.   } 

通過markStarvedLanesAsExpired的標(biāo)記,過期任務(wù)得以被放到root.expiredLanes中在隨后獲取任務(wù)優(yōu)先級(jí)時(shí),會(huì)優(yōu)先從root.expiredLanes中取值去計(jì)算優(yōu)先級(jí),這時(shí)得出的優(yōu)先級(jí)是同步級(jí)別,因此走到下面會(huì)以同步優(yōu)先級(jí)調(diào)度。實(shí)現(xiàn)過期任務(wù)被立即執(zhí)行。

  1. function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { 
  2.   // 獲取舊任務(wù) 
  3.   const existingCallbackNode = root.callbackNode; 
  4.  
  5.   // 記錄任務(wù)的過期時(shí)間,檢查是否有過期任務(wù),有則立即將它放到root.expiredLanes, 
  6.   // 便于接下來將這個(gè)任務(wù)以同步模式立即調(diào)度 
  7.   markStarvedLanesAsExpired(root, currentTime); 
  8.  
  9.   ... 
  10.  
  11.   // 若有任務(wù)過期,這里獲取到的會(huì)是同步優(yōu)先級(jí) 
  12.   const newCallbackPriority = returnNextLanesPriority(); 
  13.  
  14.   ... 
  15.  
  16.   // 調(diào)度一個(gè)新任務(wù) 
  17.   let newCallbackNode; 
  18.   if (newCallbackPriority === SyncLanePriority) { 
  19.     // 過期任務(wù)以同步優(yōu)先級(jí)被調(diào)度 
  20.     newCallbackNode = scheduleSyncCallback( 
  21.       performSyncWorkOnRoot.bind(null, root), 
  22.     ); 
  23.   } 

記錄并檢查任務(wù)是否過期

concurrent模式下的任務(wù)執(zhí)行會(huì)有時(shí)間片的體現(xiàn),檢查并記錄任務(wù)是否過期就發(fā)生在每個(gè)時(shí)間片結(jié)束交還主線程的時(shí)候??梢岳斫獬稍谡麄€(gè)(高優(yōu)先級(jí))任務(wù)的執(zhí)行期間,

持續(xù)調(diào)用ensureRootIsScheduled去做這件事,這樣一旦發(fā)現(xiàn)有過期任務(wù),可以立馬調(diào)度。

執(zhí)行任務(wù)的函數(shù)是performConcurrentWorkOnRoot,一旦因?yàn)闀r(shí)間片中斷了任務(wù),就會(huì)調(diào)用ensureRootIsScheduled。

 

  1. function performConcurrentWorkOnRoot(root) { 
  2.  
  3.   ... 
  4.  
  5.   // 去執(zhí)行更新任務(wù)的工作循環(huán),一旦超出時(shí)間片,則會(huì)退出renderRootConcurrent 
  6.   // 去執(zhí)行下面的邏輯 
  7.   let exitStatus = renderRootConcurrent(root, lanes); 
  8.  
  9.   ... 
  10.  
  11.   // 調(diào)用ensureRootIsScheduled去檢查有無過期任務(wù),是否需要調(diào)度過期任務(wù) 
  12.   ensureRootIsScheduled(root, now()); 
  13.  
  14.   // 更新任務(wù)未完成,return自己,方便Scheduler判斷任務(wù)完成狀態(tài) 
  15.   if (root.callbackNode === originalCallbackNode) { 
  16.     return performConcurrentWorkOnRoot.bind(null, root); 
  17.   } 
  18.   // 否則retutn null,表示任務(wù)已經(jīng)完成,通知Scheduler停止調(diào)度 
  19.   return null

performConcurrentWorkOnRoot是被Scheduler持續(xù)執(zhí)行的,這與Scheduler的原理相關(guān),可以移步到我寫的一篇長文幫你徹底搞懂React的調(diào)度機(jī)制原理這篇文章去了解一下,如果暫時(shí)不了解也沒關(guān)系,你只需要知道它會(huì)被Scheduler在每一個(gè)時(shí)間片內(nèi)都調(diào)用一次即可。

一旦時(shí)間片中斷了任務(wù),那么就會(huì)走到下面調(diào)用ensureRootIsScheduled。我們可以追問一下時(shí)間片下的fiber樹構(gòu)建機(jī)制,更深入的理解ensureRootIsScheduled

為什么會(huì)在時(shí)間片結(jié)束的時(shí)候調(diào)用。

這一切都要從renderRootConcurrent函數(shù)說起:

 

  1. function renderRootConcurrent(root: FiberRoot, lanes: Lanes) { 
  2.  
  3.   // workLoopConcurrent中判斷超出時(shí)間片了, 
  4.   // 那workLoopConcurrent就會(huì)從調(diào)用棧彈出, 
  5.   // 走到下面的break,終止循環(huán) 
  6.  
  7.   // 然后走到循環(huán)下面的代碼 
  8.   // 就說明是被時(shí)間片打斷任務(wù)了,或者fiber樹直接構(gòu)建完了 
  9.   // 依據(jù)情況return不同的status 
  10.   do { 
  11.     try { 
  12.       workLoopConcurrent(); 
  13.       break; 
  14.     } catch (thrownValue) { 
  15.       handleError(root, thrownValue); 
  16.     } 
  17.   } while (true); 
  18.  
  19.  
  20.   if (workInProgress !== null) { 
  21.       // workInProgress 不為null,說明是被時(shí)間片打斷的 
  22.       // return RootIncomplete說明還沒完成任務(wù) 
  23.     return RootIncomplete; 
  24.   } else { 
  25.  
  26.     // 否則說明任務(wù)完成了 

renderRootConcurrent中寫了一個(gè)do...while(true)的循環(huán),目的是如果任務(wù)執(zhí)行的時(shí)間未超出時(shí)間片限制(一般未5ms),那就一直執(zhí)行,

直到workLoopConcurrent調(diào)用完成出棧,brake掉循環(huán)。

workLoopConcurrent中依據(jù)時(shí)間片去深度優(yōu)先構(gòu)建fiber樹

 

  1. function workLoopConcurrent() { 
  2.   // 調(diào)用shouldYield判斷如果超出時(shí)間片限制,那么結(jié)束循環(huán) 
  3.   while (workInProgress !== null && !shouldYield()) { 
  4.     performUnitOfWork(workInProgress); 
  5.   } 

所以整個(gè)持續(xù)檢查過期任務(wù)過程是:一個(gè)更新任務(wù)被調(diào)度,Scheduler調(diào)用performConcurrentWorkOnRoot去執(zhí)行任務(wù),后面的步驟:

  1. performConcurrentWorkOnRoot調(diào)用renderRootConcurrent,renderRootConcurrent去調(diào)用workLoopConcurrent執(zhí)行fiber的構(gòu)建任務(wù),也就是update引起的更新任務(wù)。
  2. 當(dāng)執(zhí)行時(shí)間超出時(shí)間片限制之后,首先workLoopConcurrent會(huì)彈出調(diào)用棧,然后renderRootConcurrent中的do...while(true)被break掉,使得它也彈出調(diào)用棧,因此回到performConcurrentWorkOnRoot中。
  3. performConcurrentWorkOnRoot繼續(xù)往下執(zhí)行,調(diào)用ensureRootIsScheduled檢查有無過期任務(wù)需要被調(diào)度。
  4. 本次時(shí)間片跳出后的邏輯完成,Scheduler會(huì)再次調(diào)用performConcurrentWorkOnRoot執(zhí)行任務(wù),重復(fù)1到3的過程,也就實(shí)現(xiàn)了持續(xù)檢查過期任務(wù)。

總結(jié)

低優(yōu)先級(jí)任務(wù)的饑餓問題其實(shí)本質(zhì)上還是高優(yōu)先級(jí)任務(wù)插隊(duì),但是低優(yōu)先級(jí)任務(wù)在被長時(shí)間的打斷之后,它的優(yōu)先級(jí)并沒有提高,提高的根本原因是markStarvedLanesAsExpired

將過期任務(wù)的優(yōu)先級(jí)放入root.expiredLanes,之后優(yōu)先從expiredLanes獲取任務(wù)優(yōu)先級(jí)以及渲染優(yōu)先級(jí),即使pendingLanes中有更高優(yōu)先級(jí)的任務(wù),但也無法從pendingLanes中

獲取到高優(yōu)任務(wù)對(duì)應(yīng)的任務(wù)優(yōu)先級(jí)。

責(zé)任編輯:未麗燕 來源: segmentfault.com
相關(guān)推薦

2021-02-02 14:55:48

React前端高優(yōu)先

2021-08-03 07:40:47

宏任務(wù)微任務(wù)React

2012-02-14 09:13:51

程序員

2023-08-27 15:28:53

人工智能語言模型

2021-05-21 06:13:35

React Hooks react-refrReact

2020-04-15 09:20:08

數(shù)據(jù)護(hù)欄行為分析數(shù)據(jù)庫安全

2018-01-24 18:00:21

LinuxDebianvim

2021-01-03 09:58:39

StampedLock線程開發(fā)技術(shù)

2013-12-17 10:15:19

OpenMP任務(wù)調(diào)度

2009-11-23 09:34:05

WPF本質(zhì)

2019-10-24 12:13:59

Android APP后臺(tái)動(dòng)畫

2022-08-27 14:14:06

Spring事務(wù)開發(fā)

2023-04-06 00:22:19

JavaScrip任務(wù)開發(fā)

2024-05-17 08:30:11

Activity移動(dòng)屬性

2023-04-26 07:46:22

React隱藏彩蛋

2016-11-23 16:48:20

react-nativandroidjavascript

2009-03-16 09:16:13

行為擴(kuò)展WCF.NET

2020-11-04 17:51:06

人工智能畜牧業(yè)技術(shù)

2022-04-14 09:01:39

React源碼Flow

2009-06-01 11:41:53

SilverlightSilverlight拖放
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)