溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

React中的任務(wù)饑餓行為是什么意思

發(fā)布時(shí)間:2021-07-20 00:18:14 來源:億速云 閱讀:419 作者:chen 欄目:web開發(fā)

本篇內(nèi)容主要講解“React中的任務(wù)饑餓行為是什么意思”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“React中的任務(wù)饑餓行為是什么意思”吧!

本文是在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ù):

React中的任務(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中的任務(wù)饑餓行為是什么意思

要做到這樣,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:

0 b 0 0 1 1 0 0 0

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

[ -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中。

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

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

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

export function markStarvedLanesAsExpired(   root: FiberRoot,   currentTime: number, ): void {   // 獲取root.pendingLanes   const pendingLanes = root.pendingLanes;   // suspense相關(guān)   const suspendedLanes = root.suspendedLanes;   // suspense的任務(wù)被恢復(fù)的lanes   const pingedLanes = root.pingedLanes;    // 獲取root上已有的過期時(shí)間   const expirationTimes = root.expirationTimes;    // 遍歷待處理的lanes,檢查是否到了過期時(shí)間,如果過期,   // 這個(gè)更新被視為饑餓狀態(tài),并把它的lane放到expiredLanes    let lanes = pendingLanes;   while (lanes > 0) {      /*      pickArbitraryLaneIndex是找到lanes中最靠左的那個(gè)1在lanes中的index      也就是獲取到當(dāng)前這個(gè)lane在expirationTimes中對(duì)應(yīng)的index      比如 0b0010,得出的index就是2,就可以去expirationTimes中獲取index為2      位置上的過期時(shí)間     */      const index = pickArbitraryLaneIndex(lanes);     const lane = 1 << index;     // 上邊兩行的計(jì)算過程舉例如下:     //   lanes = 0b0000000000000000000000000011100     //   index = 4      //       1 = 0b0000000000000000000000000000001     //  1 << 4 = 0b0000000000000000000000000001000      //    lane = 0b0000000000000000000000000001000      const expirationTime = expirationTimes[index];     if (expirationTime === NoTimestamp) {       // Found a pending lane with no expiration time. If it's not suspended, or       // if it's pinged, assume it's CPU-bound. Compute a new expiration time       // using the current time.       // 發(fā)現(xiàn)一個(gè)沒有過期時(shí)間并且待處理的lane,如果它沒被掛起,       // 或者被觸發(fā)了,那么去計(jì)算過期時(shí)間       if (         (lane & suspendedLanes) === NoLanes ||         (lane & pingedLanes) !== NoLanes       ) {          expirationTimes[index] = computeExpirationTime(lane, currentTime);       }     } else if (expirationTime <= currentTime) {       // This lane expired       // 已經(jīng)過期,將lane并入到expiredLanes中,實(shí)現(xiàn)了將lanes標(biāo)記為過期       root.expiredLanes |= lane;     }     // 將lane從lanes中刪除,每循環(huán)一次刪除一個(gè),直到lanes清空成0,結(jié)束循環(huán)     lanes &= ~lane;   } }

通過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í)行。

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

記錄并檢查任務(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。

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

performConcurrentWorkOnRoot是被Scheduler持續(xù)執(zhí)行的,這與Scheduler的原理相關(guān),可以移步到我寫的一篇長(zhǎng)文幫你徹底搞懂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ù)說起:

function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {    // workLoopConcurrent中判斷超出時(shí)間片了,   // 那workLoopConcurrent就會(huì)從調(diào)用棧彈出,   // 走到下面的break,終止循環(huán)    // 然后走到循環(huán)下面的代碼   // 就說明是被時(shí)間片打斷任務(wù)了,或者fiber樹直接構(gòu)建完了   // 依據(jù)情況return不同的status   do {     try {       workLoopConcurrent();       break;     } catch (thrownValue) {       handleError(root, thrownValue);     }   } while (true);     if (workInProgress !== null) {       // workInProgress 不為null,說明是被時(shí)間片打斷的       // return RootIncomplete說明還沒完成任務(wù)     return RootIncomplete;   } else {      // 否則說明任務(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樹

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

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

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. performConcurrentWorkOnRoot調(diào)用renderRootConcurrent,renderRootConcurrent去調(diào)用workLoopConcurrent執(zhí)行fiber的構(gòu)建任務(wù),也就是update引起的更新任務(wù)。

  3. 當(dāng)執(zhí)行時(shí)間超出時(shí)間片限制之后,首先workLoopConcurrent會(huì)彈出調(diào)用棧,然后renderRootConcurrent中的do...while(true)被break掉,使得它也彈出調(diào)用棧,因此回到performConcurrentWorkOnRoot中。

  4. performConcurrentWorkOnRoot繼續(xù)往下執(zhí)行,調(diào)用ensureRootIsScheduled檢查有無過期任務(wù)需要被調(diào)度。

  5. 本次時(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ù)在被長(zhǎng)時(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í)。

到此,相信大家對(duì)“React中的任務(wù)饑餓行為是什么意思”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI