本篇內(nèi)容主要講解“React中的任務(wù)饑餓行為是什么意思”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“React中的任務(wù)饑餓行為是什么意思”吧!
成都創(chuàng)新互聯(lián)公司堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站設(shè)計(jì)、做網(wǎng)站、成都外貿(mào)網(wǎng)站建設(shè)公司、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的盂縣網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
本文是在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:
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ù),后面的步驟:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
performConcurrentWorkOnRoot調(diào)用renderRootConcurrent,renderRootConcurrent去調(diào)用workLoopConcurrent執(zhí)行fiber的構(gòu)建任務(wù),也就是update引起的更新任務(wù)。
當(dāng)執(zhí)行時(shí)間超出時(shí)間片限制之后,首先workLoopConcurrent會(huì)彈出調(diào)用棧,然后renderRootConcurrent中的do...while(true)被break掉,使得它也彈出調(diào)用棧,因此回到performConcurrentWorkOnRoot中。
performConcurrentWorkOnRoot繼續(xù)往下執(zhí)行,調(diào)用ensureRootIsScheduled檢查有無過期任務(wù)需要被調(diào)度。
本次時(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í)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!