摘要:布爾型,表示該幀里面沒有執(zhí)行回調(diào),超時(shí)了。這一處理機(jī)制在監(jiān)聽函數(shù)中實(shí)現(xiàn)作為,接受消息的時(shí)機(jī)將隨著線程的空閑程度起變化。
為什么是要有scheduler
首先要從js的是單線程模型來說起,Javascript執(zhí)行是會(huì)經(jīng)歷靜態(tài)編譯,動(dòng)態(tài)解釋和事件循環(huán)做任務(wù)調(diào)度的過程,大致的流程如下(注意,該流程是以chrome瀏覽器內(nèi)核為標(biāo)準(zhǔn)的執(zhí)行流程,在node或者其他瀏覽器中,執(zhí)行流程會(huì)有所差異,但是核心思想是差不多的。從這里面我們很直觀的認(rèn)識(shí)到j(luò)s的線程模型是怎么工作的。那跟scheduler有什么關(guān)系呢,我們都知道React16采用了fiber架構(gòu),這樣的架構(gòu)下,使用鏈表結(jié)構(gòu)代替原有的函數(shù)嵌套,避免無法控制組件渲染的過程的問題,F(xiàn)iber讓React內(nèi)部會(huì)動(dòng)態(tài)靈活的管理所有組件的渲染任務(wù),可以中斷暫停某一個(gè)組件的渲染,所以,對(duì)于復(fù)雜型應(yīng)用來說,對(duì)于某一個(gè)交互動(dòng)作的反饋型任務(wù),我們是可以對(duì)其進(jìn)行拆解,一步步的做交互反饋,避免在一個(gè)頁面重繪時(shí)間周期內(nèi)做過多的事情,這樣就能減少應(yīng)用的長任務(wù),最大化提升應(yīng)用操作性能,更好的利用有限的時(shí)間,那么,我們現(xiàn)在可以只聚焦在任務(wù)管理上,一起來研究一下React到底是如何調(diào)度組件的任務(wù)執(zhí)行的,這里說渲染感覺不太準(zhǔn)確
macrotask: setTimeout, setInterval, setImmediate,MessageChannel, I/O, UI rendering netWork
microtask: process.nextTick, Promises, Object.observe(廢棄), MutationObserver
[引自大神論述]
上圖來自知乎文章
主線程負(fù)責(zé)解析編譯和調(diào)度異步事件循環(huán)調(diào)度
異步隊(duì)列和V8通訊 通過polling Check來實(shí)現(xiàn)
異步隊(duì)列分成macotask 和 microtask
macrotask一般情況下,在沒有特別說明的情況下我們會(huì)把macrotask稱為task queues ,在一次的事件循環(huán)中,他只會(huì)執(zhí)行一次microtask
在每一次事件循環(huán)中,macrotask 只會(huì)提取一個(gè)執(zhí)行,而 microtask 會(huì)一直提取,直到 microtasks 隊(duì)列清空。
那么也就是說如果我的某個(gè) microtask 任務(wù)又推入了一個(gè)任務(wù)進(jìn)入 microtasks 隊(duì)列,那么在主線程完成該任務(wù)之后,仍然會(huì)繼續(xù)運(yùn)行 microtasks 任務(wù)直到任務(wù)隊(duì)列耗盡
而事件循環(huán)每次只會(huì)入棧一個(gè) macrotask ,主線程執(zhí)行完該任務(wù)后又會(huì)先檢查 microtasks 隊(duì)列并完成里面的所有任務(wù)后再執(zhí)行 macrotask
console.log("start"); setTimeout(function() { console.log("macrotask"); }, 0); Promise.resolve().then(function() { console.log("microtask"); }).then(function() { console.log("microtask"); }); console.log(" end");
根據(jù)上述理論自己試試程序的運(yùn)行結(jié)果, 為什么我們?cè)诜治鰏cheduler源碼之前先要介紹下異步隊(duì)列,因?yàn)榱私馇宄s異步隊(duì)列才會(huì)讓我們更加清晰知道scheduler是怎么使用調(diào)度方法來更好的安排代碼執(zhí)行時(shí)機(jī)。
瀏覽器的執(zhí)行頁面繪制過程
圖片出自同一地方
執(zhí)行JS(具體流程在上面有描述)--->計(jì)算Style--->構(gòu)建布局模型(Layout)--->繪制圖層樣式(Paint)--->組合計(jì)算渲染呈現(xiàn)結(jié)果(Composite)
一個(gè)完成的過程稱之為一幀
一般設(shè)備的刷新頻率是60Hz (還有更大的 scheduler 最大設(shè)定為 120hz) 也就是按照理想情況來說一秒鐘有60幀 那么一幀的平均時(shí)間是 1000 / 60 大約是 16.7ms 也就是我們一幀的執(zhí)行時(shí)間不能超過 16.7 否則就會(huì)出現(xiàn)丟失幀和卡頓情況
幀的渲染是在處理完流程之后進(jìn)行的
幀的渲染是在獨(dú)立的UI線程去執(zhí)行 是有GPU等
剩余的時(shí)間為空閑時(shí)間
在離散型 交互動(dòng)作中不一定要求需要16.7 ms的時(shí)間
對(duì)于離散型交互,上一幀的渲染到下一幀的渲染時(shí)間是屬于系統(tǒng)空閑時(shí)間,經(jīng)過親測(cè),Input輸入,最快的單字符輸入時(shí)間平均是33ms(通過持續(xù)按同一個(gè)鍵來觸發(fā)),相當(dāng)于,上一幀到下一幀中間會(huì)存在大于16.4ms的空閑時(shí)間,就是說任何離散型交互,最小的系統(tǒng)空閑時(shí)間也有16.4ms,也就是說,離散型交互的最短幀長一般是33ms
requestIdleCallback在幀的渲染中當(dāng)執(zhí)行完流程和UI繪制之后 會(huì)有一部分空閑時(shí)間,如果我們能掌握這個(gè)時(shí)間加一充分利用就更加理想
那如何知道一幀進(jìn)入這個(gè)空閑時(shí)間呢,瀏覽器目前提供了這個(gè)回調(diào) requestIdleCallback 即瀏覽器空閑時(shí)
var handle = window.requestIdleCallback(callback[, options]);
requestIdleCallback回調(diào)調(diào)用時(shí)機(jī)是在回調(diào)注冊(cè)完成的上一幀渲染到下一幀渲染之間的空閑時(shí)間執(zhí)行
callback 是要執(zhí)行的回調(diào)函數(shù),會(huì)傳入 deadline 對(duì)象作為參數(shù),deadline 包含:
timeRemaining:剩余時(shí)間,單位 ms,指的是該幀剩余時(shí)間。
didTimeout:布爾型,true 表示該幀里面沒有執(zhí)行回調(diào),超時(shí)了。
option: {
timeout : 即超時(shí)時(shí)間, 不提供瀏覽器自己去計(jì)算
}
如果給定 timeout,那到了時(shí)間,不管有沒有剩余時(shí)間,都會(huì)立刻執(zhí)行回調(diào) callback。
requestAnimationFrame以前我們知道: requestAnimationFrame回調(diào)只會(huì)在當(dāng)前頁面激活狀態(tài)下執(zhí)行,可以大大節(jié)省CPU開銷
requestAnimationFrame回調(diào)參數(shù)是回調(diào)被調(diào)用的時(shí)間,也就是當(dāng)前幀的起始時(shí)間(可以通過這個(gè)時(shí)間去判斷 到底有么有超時(shí))
系統(tǒng)控制回調(diào)的執(zhí)行時(shí)機(jī)恰好在回調(diào)注冊(cè)完成后的下一幀渲染周期的起點(diǎn)的開始執(zhí)行,控制js計(jì)算的到屏幕響應(yīng)的精確性,避免步調(diào)不一致而導(dǎo)致丟幀
目前瀏覽器對(duì)于requestIdleCallback的支持不是特別完整,所以react團(tuán)隊(duì)放棄了requestIdleCallback的使用
自己用requestAnimationFrame和MessageChannel來polyfill
很簡(jiǎn)單,33毫秒,但是時(shí)間并不總是33ms,這個(gè)時(shí)間是React認(rèn)為的一個(gè)可以接受的最大值,如果運(yùn)行設(shè)備能做到大于30fps,那么它會(huì)去調(diào)整這個(gè)值(通常情況下可以調(diào)整到16.6ms)。調(diào)整策略是用當(dāng)前每幀的總時(shí)間與實(shí)際每幀的時(shí)間進(jìn)行比較,當(dāng)實(shí)際時(shí)間小于當(dāng)前時(shí)間且穩(wěn)定(前后兩次都小于當(dāng)前時(shí)間),那么就會(huì)認(rèn)為這個(gè)值是有效的,然后將每幀時(shí)間調(diào)整為該值(取前后兩次中時(shí)間大的值),還有就是requestAnimationFrame回調(diào)的第一個(gè)參數(shù),每一幀的起始時(shí)間,最終借助requestAnimationFrame讓一批扁平的任務(wù)恰好控制在一塊一塊的33ms這樣的時(shí)間片內(nèi)執(zhí)行即可
所有準(zhǔn)備工作都做好了, 接下來我們逐步來分析Scheduler源碼
1、 調(diào)度基本常量定義// 枚舉 // 立即執(zhí)行的任務(wù) var ImmediatePriority = 1; // 用戶阻塞優(yōu)先級(jí) var UserBlockingPriority = 2; // 一般的優(yōu)先級(jí) var NormalPriority = 3; // 低級(jí)的優(yōu)先級(jí) var LowPriority = 4; // 空閑的優(yōu)先級(jí) var IdlePriority = 5; // 我們可以理解 優(yōu)先級(jí)越高 過期時(shí)間就越短 反之 越長 // Max 31 bit integer. The max integer size in V8 for 32-bit systems. // Math.pow(2, 30) - 1 // 0b111111111111111111111111111111 // 最大整數(shù) var maxSigned31BitInt = 1073741823; // Times out immediately // 超時(shí)的優(yōu)先級(jí)時(shí)間 說明沒有剩余時(shí)間了 需要立即被調(diào)度 var IMMEDIATE_PRIORITY_TIMEOUT = -1; // Eventually times out // 事件的過期時(shí)間 250 ms var USER_BLOCKING_PRIORITY = 250; // 一般優(yōu)先級(jí)的過期時(shí)間 5000 ms var NORMAL_PRIORITY_TIMEOUT = 5000; // 優(yōu)先級(jí)低的 10000ms var LOW_PRIORITY_TIMEOUT = 10000; // Never times out 空閑的任務(wù) 有沒有限制了 也就是最大整數(shù) var IDLE_PRIORITY = maxSigned31BitInt;2、 調(diào)度所需變量的定義
// Callbacks are stored as a circular, doubly linked list. // 回調(diào)保存為了循環(huán)的雙向鏈表 var firstCallbackNode = null; // 當(dāng)前是否過期 var currentDidTimeout = false; // Pausing the scheduler is useful for debugging. // 調(diào)度是否中斷 var isSchedulerPaused = false; // 默認(rèn)當(dāng)前的優(yōu)先級(jí)為一般優(yōu)先級(jí) var currentPriorityLevel = NormalPriority; // 當(dāng)前時(shí)間開始時(shí)間 var currentEventStartTime = -1; // 當(dāng)前過期時(shí)間 var currentExpirationTime = -1; // This is set when a callback is being executed, to prevent re-entrancy. // 當(dāng)前是否執(zhí)行callback 調(diào)度 var isExecutingCallback = false; // 是否有回調(diào)唄調(diào)度 var isHostCallbackScheduled = false; // 支持performance.now 函數(shù) var hasNativePerformanceNow = typeof performance === "object" && typeof performance.now === "function";3、 調(diào)度方法 unstable_scheduleCallback
function unstable_scheduleCallback(callback, deprecated_options) { //開始時(shí)間 var startTime = currentEventStartTime !== -1 ? currentEventStartTime : exports.unstable_now(); var expirationTime; // 過期時(shí)間 這里模擬的是 requestIdleCallback options的 timeout的定義 // 如果這里指定了 timeout 就會(huì)計(jì)算出 過期時(shí)間 // 如果么有指定就會(huì)根據(jù) 調(diào)度程序的優(yōu)先級(jí)去計(jì)算 比如 普通是 5000 低級(jí)是 10000 空閑就永遠(yuǎn)不會(huì)過期等.... if (typeof deprecated_options === "object" && deprecated_options !== null && typeof deprecated_options.timeout === "number") { // FIXME: Remove this branch once we lift expiration times out of React. expirationTime = startTime + deprecated_options.timeout; } else { switch (currentPriorityLevel) { case ImmediatePriority: expirationTime = startTime + IMMEDIATE_PRIORITY_TIMEOUT; break; case UserBlockingPriority: expirationTime = startTime + USER_BLOCKING_PRIORITY; break; case IdlePriority: expirationTime = startTime + IDLE_PRIORITY; break; case LowPriority: expirationTime = startTime + LOW_PRIORITY_TIMEOUT; break; case NormalPriority: default: expirationTime = startTime + NORMAL_PRIORITY_TIMEOUT; } } // 新建一個(gè)節(jié)點(diǎn) var newNode = { callback: callback, priorityLevel: currentPriorityLevel, expirationTime: expirationTime, next: null, previous: null }; // Insert the new callback into the list, ordered first by expiration, then // by insertion. So the new callback is inserted any other callback with // equal expiration. // 將新回調(diào)插入列表,首先按到期排序,然后按插入排序。所以新的回調(diào)插入到任何callback都擁有相同的過期時(shí)間 // 如果鏈表是空的 則 重新構(gòu)建 if (firstCallbackNode === null) { // This is the first callback in the list. firstCallbackNode = newNode.next = newNode.previous = newNode; ensureHostCallbackIsScheduled(); } else { var next = null; var node = firstCallbackNode; // 先將鏈表根據(jù)過期時(shí)間進(jìn)行排序 遍歷查找 尋找比當(dāng)前過期時(shí)間大的節(jié)點(diǎn) do { if (node.expirationTime > expirationTime) { // The new callback expires before this one. next = node; break; } node = node.next; } while (node !== firstCallbackNode); // 沒有找到比當(dāng)前更靠后的 元素 說明當(dāng)前的節(jié)點(diǎn)是最不優(yōu)先的 if (next === null) { // No callback with a later expiration was found, which means the new // callback has the latest expiration in the list. // 當(dāng)前新加入的節(jié)點(diǎn)是最后面的 指針指向鏈表頭 next = firstCallbackNode; } else if (next === firstCallbackNode) { // The new callback has the earliest expiration in the entire list. // 說明所有的任務(wù) firstCallbackNode = newNode; ensureHostCallbackIsScheduled(); } //將新的node 加入到鏈表 維護(hù)一下循環(huán)鏈表 var previous = next.previous; previous.next = next.previous = newNode; newNode.next = next; newNode.previous = previous; } return newNode; }4. ensureHostCallbackIsScheduled 遇到優(yōu)先級(jí)高的 需要特別處理
function ensureHostCallbackIsScheduled() { // 調(diào)度正在執(zhí)行 返回 也就是不能打斷已經(jīng)在執(zhí)行的 if (isExecutingCallback) { // Don"t schedule work yet; wait until the next time we yield. return; } // Schedule the host callback using the earliest expiration in the list. // 讓優(yōu)先級(jí)最高的 進(jìn)行調(diào)度 如果存在已經(jīng)在調(diào)度的 直接取消 var expirationTime = firstCallbackNode.expirationTime; if (!isHostCallbackScheduled) { isHostCallbackScheduled = true; } else { // Cancel the existing host callback. // 取消正在調(diào)度的callback cancelHostCallback(); } // 發(fā)起調(diào)度 requestHostCallback(flushWork, expirationTime); }5、requestAnimationFrameWithTimeout
var localSetTimeout = typeof setTimeout === "function" ? setTimeout : undefined; var localClearTimeout = typeof clearTimeout === "function" ? clearTimeout : undefined; // We don"t expect either of these to necessarily be defined, but we will error // later if they are missing on the client. var localRequestAnimationFrame = typeof requestAnimationFrame === "function" ? requestAnimationFrame : undefined; var localCancelAnimationFrame = typeof cancelAnimationFrame === "function" ? cancelAnimationFrame : undefined; // requestAnimationFrame does not run when the tab is in the background. If // we"re backgrounded we prefer for that work to happen so that the page // continues to load in the background. So we also schedule a "setTimeout" as // a fallback. // TODO: Need a better heuristic for backgrounded work. var ANIMATION_FRAME_TIMEOUT = 100; var rAFID; var rAFTimeoutID; /** * 是解決網(wǎng)頁選項(xiàng)卡如果在未激活狀態(tài)下requestAnimationFrame不會(huì)被觸發(fā)的問題, *這樣的話,調(diào)度器是可以在后臺(tái)繼續(xù)做調(diào)度的,一方面也能提升用戶體驗(yàn), * 同時(shí)后臺(tái)執(zhí)行的時(shí)間間隔是以100ms為步長,這個(gè)是一個(gè)最佳實(shí)踐,100ms是不會(huì)影響用戶體驗(yàn)同時(shí)也不影響CPU能耗的一個(gè)折中時(shí)間間隔 為什么要用 settimeout 因?yàn)閞equestAnimationFrame不會(huì)在tab不激活的情況下不執(zhí)行 */ var requestAnimationFrameWithTimeout = function (callback) { // schedule rAF and also a setTimeout rAFID = localRequestAnimationFrame(function (timestamp) { // cancel the setTimeout localClearTimeout(rAFTimeoutID); callback(timestamp); }); rAFTimeoutID = localSetTimeout(function () { // cancel the requestAnimationFrame localCancelAnimationFrame(rAFID); callback(exports.unstable_now()); }, ANIMATION_FRAME_TIMEOUT); }; if (hasNativePerformanceNow) { var Performance = performance; exports.unstable_now = function () { return Performance.now(); }; } else { exports.unstable_now = function () { return localDate.now(); }; }6、調(diào)度requestHostCallback
這里react 做了特別的兼容處理 注入方式和不支持window或者 MessageChannel的方式 這里不做主要分析 因?yàn)?br>比較簡(jiǎn)單,這里將主要研究現(xiàn)代瀏覽器的處理方式
var requestHostCallback; var cancelHostCallback; var shouldYieldToHost; var globalValue = null; if (typeof window !== "undefined") { globalValue = window; } else if (typeof global !== "undefined") { globalValue = global; } if (globalValue && globalValue._schedMock) { // Dynamic injection, only for testing purposes. // 動(dòng)態(tài)注入 用于測(cè)試目的 var globalImpl = globalValue._schedMock; requestHostCallback = globalImpl[0]; cancelHostCallback = globalImpl[1]; shouldYieldToHost = globalImpl[2]; exports.unstable_now = globalImpl[3]; } else if ( // 非DOM環(huán)境 // If Scheduler runs in a non-DOM environment, it falls back to a naive // implementation using setTimeout. typeof window === "undefined" || // Check if MessageChannel is supported, too. typeof MessageChannel !== "function") { // If this accidentally gets imported in a non-browser environment, e.g. JavaScriptCore, // fallback to a naive implementation. var _callback = null; var _flushCallback = function (didTimeout) { if (_callback !== null) { try { _callback(didTimeout); } finally { _callback = null; } } }; requestHostCallback = function (cb, ms) { //這里的調(diào)度就直接在settimeout 去執(zhí)行 也就是直接放入macrotask隊(duì)列 這里應(yīng)該是下下策 if (_callback !== null) { // Protect against re-entrancy. setTimeout(requestHostCallback, 0, cb); } else { _callback = cb; setTimeout(_flushCallback, 0, false); } }; cancelHostCallback = function () { _callback = null; }; shouldYieldToHost = function () { return false; }; } else { if (typeof console !== "undefined") { // TODO: Remove fb.me link if (typeof localRequestAnimationFrame !== "function") { console.error("This browser doesn"t support requestAnimationFrame. " + "Make sure that you load a " + "polyfill in older browsers. https://fb.me/react-polyfills"); } if (typeof localCancelAnimationFrame !== "function") { console.error("This browser doesn"t support cancelAnimationFrame. " + "Make sure that you load a " + "polyfill in older browsers. https://fb.me/react-polyfills"); } } // 調(diào)度的callback var scheduledHostCallback = null; // 消息發(fā)送中標(biāo)識(shí) var isMessageEventScheduled = false; // 過期時(shí)間 var timeoutTime = -1; // rAF 輪詢啟動(dòng)狀態(tài) var isAnimationFrameScheduled = false; // 任務(wù)執(zhí)行中標(biāo)識(shí) var isFlushingHostCallback = false; // 下一幀期望完成時(shí)間點(diǎn),用于判斷重繪后 js 線程是否空閑,還是長期占用 var frameDeadline = 0; // We start out assuming that we run at 30fps but then the heuristic tracking // will adjust this value to a faster fps if we get more frequent animation // frames. /** * 我們假設(shè)我們以30fps運(yùn)行,然后進(jìn)行啟發(fā)式跟蹤 如果我們獲得更頻繁的動(dòng)畫,我會(huì)將此值調(diào)整為更快的fps 幀 默認(rèn)33 為什么是33 因?yàn)槲覀兗俣棵?0幀固定評(píng)率刷新 也就是 一幀需要33ms */ var previousFrameTime = 33; var activeFrameTime = 33; //以此推斷線程是否空閑,好添加并處理新任 shouldYieldToHost = function () { return frameDeadline <= exports.unstable_now(); }; // We use the postMessage trick to defer idle work until after the repaint. // 使用postMessage 來跟蹤判斷重繪是否完成 var channel = new MessageChannel(); var port = channel.port2; // 當(dāng)port1 發(fā)送消息后 這里在幀重繪完成后 進(jìn)入message回調(diào) 接著處理我們 // callback channel.port1.onmessage = function (event) { isMessageEventScheduled = false; var prevScheduledCallback = scheduledHostCallback; var prevTimeoutTime = timeoutTime; scheduledHostCallback = null; timeoutTime = -1; var currentTime = exports.unstable_now(); var didTimeout = false; // 說明沒有時(shí)間了 當(dāng)前幀給與這個(gè)callback的時(shí)間沒有了 if (frameDeadline - currentTime <= 0) { // There"s no time left in this idle period. Check if the callback has // a timeout and whether it"s been exceeded. // 檢查當(dāng)前callback 是否過期 和 是否被執(zhí)行 // previousFrameTime 小于等于 currentTime 時(shí),scheduler // 認(rèn)為線程不是空閑的,對(duì)于超時(shí)的任務(wù)將立即執(zhí)行, // 對(duì)于未超時(shí)的任務(wù)將在下次重繪后予以處理 // 顯然是超時(shí)的 并且沒有被取消 直接執(zhí)行 并且給與timeout 為空 if (prevTimeoutTime !== -1 && prevTimeoutTime <= currentTime) { // Exceeded the timeout. Invoke the callback even though there"s no // time left. didTimeout = true; } else { // No timeout. //沒有超時(shí) 如果沒有安排輪詢 就開啟輪詢 if (!isAnimationFrameScheduled) { // Schedule another animation callback so we retry later. isAnimationFrameScheduled = true; requestAnimationFrameWithTimeout(animationTick); } // Exit without invoking the callback. // 不執(zhí)行callback 直接退出 讓輪詢?nèi)フ{(diào)度 scheduledHostCallback = prevScheduledCallback; timeoutTime = prevTimeoutTime; return; } } // 執(zhí)行callback 這里應(yīng)該是終點(diǎn)了 到這里為止 調(diào)度分析完了 // 執(zhí)行完成的調(diào)度 返回的只有 是否已經(jīng)過期(didTimeout) if (prevScheduledCallback !== null) { isFlushingHostCallback = true; try { prevScheduledCallback(didTimeout); } finally { isFlushingHostCallback = false; } } }; // 這里作為rAF的callback 處理函數(shù) /** * 在 animateTick 中,scheduler 將計(jì)算下一幀期望完成時(shí)間點(diǎn) previousFrameTime, 然后通過 port.postMessage 方法發(fā)送消息。等到 port1 接受到消息時(shí),schdulear 將 previousFrameTime 與 currentTime 作比較:當(dāng) previousFrameTime 小于等于 currentTime 時(shí), scheduler 認(rèn)為線程不是空閑的,對(duì)于超時(shí)的任務(wù)將立即執(zhí)行,對(duì)于未超時(shí)的任務(wù)將在下次重繪后予以處理; 當(dāng) previousFrameTime 大于 currentTime 時(shí),線程就是空閑的,scheduler 將立即執(zhí)行。這一處理機(jī)制在 port1.onMessage 監(jiān)聽函數(shù)中實(shí)現(xiàn)(作為 macrotasks,port1 接受消息的時(shí)機(jī)將隨著線程的空閑程度起變化)。 */ var animationTick = function (rafTime) { //輪詢了 這里進(jìn)入 if (scheduledHostCallback !== null) { // Eagerly schedule the next animation callback at the beginning of the // frame. If the scheduler queue is not empty at the end of the frame, it // will continue flushing inside that callback. If the queue *is* empty, // then it will exit immediately. Posting the callback at the start of the // frame ensures it"s fired within the earliest possible frame. If we // waited until the end of the frame to post the callback, we risk the // browser skipping a frame and not firing the callback until the frame // after that. /** * * 最先在幀的開頭安排下一個(gè)回調(diào)。如果調(diào)度程序隊(duì)列在幀的末尾不為空, * 它將繼續(xù)在該回調(diào)內(nèi)刷新。如果隊(duì)列*為*空,則它將立即退出 *。在幀的開頭觸發(fā)回調(diào)可確保在最早的幀內(nèi)觸發(fā)。要是我們 *等到幀結(jié)束后觸發(fā)回調(diào),我們冒著瀏覽器丟幀的風(fēng)險(xiǎn), *并且在此幀之后的不會(huì)觸發(fā)回調(diào)。 */ requestAnimationFrameWithTimeout(animationTick); } else { // No pending work. Exit. isAnimationFrameScheduled = false; return; } // 調(diào)度的時(shí)間rafTime - frameDeadline 下一幀預(yù)到期 + 一幀的多少 = 給下一幀留下的時(shí)間 var nextFrameTime = rafTime - frameDeadline + activeFrameTime; // 幀的頻率小于當(dāng)前的 說明處理的時(shí)間都是比較短的 // 其實(shí)這里做了下調(diào)整 如果當(dāng)前的設(shè)備的更新頻率大于我們?cè)O(shè)定的 30fps // 我們就需要取更新的頻率的最大值 這里的最大值的更新頻率 最大值 // 我們需要澄清一個(gè)問題 頻率越大 一幀花費(fèi)的時(shí)間就越短 if (nextFrameTime < activeFrameTime && previousFrameTime < activeFrameTime) { if (nextFrameTime < 8) { // 預(yù)防性代碼 也就是說 不支持刷新頻率大于120hz 如果大于120 就當(dāng)120處理 也就是說 一幀只有8ms // Defensive coding. We don"t support higher frame rates than 120hz. // If the calculated frame time gets lower than 8, it is probably a bug. nextFrameTime = 8; } // If one frame goes long, then the next one can be short to catch up. // If two frames are short in a row, then that"s an indication that we // actually have a higher frame rate than what we"re currently optimizing. // We adjust our heuristic dynamically accordingly. For example, if we"re // running on 120hz display or 90hz VR display. // Take the max of the two in case one of them was an anomaly due to // missed frame deadlines. //如果一幀長,那么下一幀可能很短。 // 如果兩個(gè)幀連續(xù)短,那么這表明我們實(shí)際上具有比我們當(dāng)前優(yōu)化 //的幀速率更高的幀速率。我們相應(yīng)地動(dòng)態(tài)調(diào)整啟發(fā)式。例如,如果我們是 // 在120hz顯示屏或90hz VR顯示屏上運(yùn)行。 // 取兩個(gè)中的最大值,以防其中一個(gè)因錯(cuò)過幀截止日期而異常。 activeFrameTime = nextFrameTime < previousFrameTime ? previousFrameTime : nextFrameTime; } else { previousFrameTime = nextFrameTime; } // 計(jì)算下一幀過期時(shí)間 frameDeadline = rafTime + activeFrameTime; // 如果還么發(fā)送消息 就觸發(fā)message 讓幀重繪完成后 進(jìn)行調(diào)度callback if (!isMessageEventScheduled) { isMessageEventScheduled = true; port.postMessage(undefined); } }; // requestHostCallback = function (callback, absoluteTimeout) { // 設(shè)置當(dāng)前的callback scheduledHostCallback = callback; // 設(shè)置過期時(shí)間 timeoutTime = absoluteTimeout; //當(dāng)前如果有任務(wù)正在執(zhí)行中(意為當(dāng)前沒有重繪任務(wù),重繪線程是空閑的) // 或者所添加的任務(wù)需要立即執(zhí)行,scheduler 直接調(diào)用 port.postMessage 發(fā)送消息,跳過 rAF // 輪詢,以使任務(wù)得到即時(shí)執(zhí)行 if (isFlushingHostCallback || absoluteTimeout < 0) { // Don"t wait for the next frame. Continue working ASAP, in a new event. port.postMessage(undefined); } else if (!isAnimationFrameScheduled) { // If rAF didn"t already schedule one, we need to schedule a frame. // 如果raf 沒有進(jìn)行調(diào)度 安排一個(gè)新的 rAF輪詢 // 如果rAF 沒有發(fā)揮作用 在使用settimeout 去作為預(yù)備去調(diào)度 // TODO: If this rAF doesn"t materialize because the browser throttles, we // might want to still have setTimeout trigger rIC as a backup to ensure // that we keep performing work. isAnimationFrameScheduled = true; //如果 rAF 輪詢未啟動(dòng),調(diào)用 requestAnimationFrameWithTimeout(animationTick) 啟動(dòng)輪詢 requestAnimationFrameWithTimeout(animationTick); } }; cancelHostCallback = function () { scheduledHostCallback = null; isMessageEventScheduled = false; timeoutTime = -1; }; }7 執(zhí)行到調(diào)度程序 callback 這里callback是怎么執(zhí)行的(flushWork)
flushFirstCallback
flushFirstCallback 從雙向鏈表中取出首個(gè)任務(wù)節(jié)點(diǎn)并執(zhí)行。若首個(gè)任務(wù)節(jié)點(diǎn)的 callback 返回函數(shù),使用該函數(shù)構(gòu)建新的 callbackNode 任務(wù)節(jié)點(diǎn),并將該任務(wù)節(jié)點(diǎn)插入雙向鏈表中:若該任務(wù)節(jié)點(diǎn)的優(yōu)先級(jí)最高、且不只包含一個(gè)任務(wù)節(jié)點(diǎn),調(diào)用 ensureHostCallbackIsScheduled,在下一次重繪后酌情執(zhí)行雙向鏈表中的任務(wù)節(jié)點(diǎn);否則只將新創(chuàng)建的任務(wù)節(jié)點(diǎn)添加到雙向鏈表中
function flushFirstCallback() { var flushedNode = firstCallbackNode; // Remove the node from the list before calling the callback. That way the // list is in a consistent state even if the callback throws. var next = firstCallbackNode.next; if (firstCallbackNode === next) { // This is the last callback in the list. firstCallbackNode = null; next = null; } else { var lastCallbackNode = firstCallbackNode.previous; firstCallbackNode = lastCallbackNode.next = next; next.previous = lastCallbackNode; } flushedNode.next = flushedNode.previous = null; // Now it"s safe to call the callback. var callback = flushedNode.callback; var expirationTime = flushedNode.expirationTime; var priorityLevel = flushedNode.priorityLevel; var previousPriorityLevel = currentPriorityLevel; var previousExpirationTime = currentExpirationTime; currentPriorityLevel = priorityLevel; currentExpirationTime = expirationTime; var continuationCallback; try { continuationCallback = callback(); } finally { // 恢復(fù)當(dāng)一次的優(yōu)先級(jí) currentPriorityLevel = previousPriorityLevel; currentExpirationTime = previousExpirationTime; } // A callback may return a continuation. The continuation should be scheduled // with the same priority and expiration as the just-finished callback //. 如果callback 返回的還是 function 需要重新調(diào)度 // 跟新加入一個(gè)節(jié)點(diǎn)是一樣的 就不在分析了 if (typeof continuationCallback === "function") { var continuationNode = { callback: continuationCallback, priorityLevel: priorityLevel, expirationTime: expirationTime, next: null, previous: null }; // Insert the new callback into the list, sorted by its expiration. This is // almost the same as the code in `scheduleCallback`, except the callback // is inserted into the list *before* callbacks of equal expiration instead // of after. if (firstCallbackNode === null) { // This is the first callback in the list. firstCallbackNode = continuationNode.next = continuationNode.previous = continuationNode; } else { var nextAfterContinuation = null; var node = firstCallbackNode; do { if (node.expirationTime >= expirationTime) { // This callback expires at or after the continuation. We will insert // the continuation *before* this callback. nextAfterContinuation = node; break; } node = node.next; } while (node !== firstCallbackNode); if (nextAfterContinuation === null) { // No equal or lower priority callback was found, which means the new // callback is the lowest priority callback in the list. nextAfterContinuation = firstCallbackNode; } else if (nextAfterContinuation === firstCallbackNode) { // The new callback is the highest priority callback in the list. firstCallbackNode = continuationNode; ensureHostCallbackIsScheduled(); } var previous = nextAfterContinuation.previous; previous.next = nextAfterContinuation.previous = continuationNode; continuationNode.next = nextAfterContinuation; continuationNode.previous = previous; } } }
flushImmediateWork
基于 flushFirstCallback,flushImmediateWork 函數(shù)用于執(zhí)行雙向鏈表中所有優(yōu)先級(jí)為 ImmediatePriority 的任務(wù)節(jié)點(diǎn)。如果雙向鏈表不只包含優(yōu)先級(jí)為 ImmediatePriority 的任務(wù)節(jié)點(diǎn),flushImmediateWork 將調(diào)用 ensureHostCallbackIsScheduled 等待下次重繪后執(zhí)行剩余的任務(wù)節(jié)點(diǎn)。
function flushImmediateWork() { if ( // Confirm we"ve exited the outer most event handler // 確認(rèn)我們退出了最外層的事件handler // 執(zhí)行所有立即執(zhí)行的callback currentEventStartTime === -1 && firstCallbackNode !== null && firstCallbackNode.priorityLevel === ImmediatePriority) { isExecutingCallback = true; try { do { flushFirstCallback(); } while ( // Keep flushing until there are no more immediate callbacks firstCallbackNode !== null && firstCallbackNode.priorityLevel === ImmediatePriority); } finally { isExecutingCallback = false; // 還有其他優(yōu)先級(jí)的 依次輪詢調(diào)度 if (firstCallbackNode !== null) { // There"s still work remaining. Request another callback. ensureHostCallbackIsScheduled(); } else { isHostCallbackScheduled = false; } } } }
flushWork
flushWork 作為 requestHostCallback 函數(shù)的參數(shù),獲得的首個(gè)實(shí)參 didTimeout 為是否超時(shí)的標(biāo)識(shí)。如果超時(shí),flushWork 通過調(diào)用 flushFirstCallback 批量執(zhí)行所有未超時(shí)的任務(wù)節(jié)點(diǎn);若果沒有超時(shí),flushWork 將在下一幀未完成前(通過 shouldYieldToHost 函數(shù)判斷)盡可能地執(zhí)行任務(wù)節(jié)點(diǎn)。等上述條件邏輯執(zhí)行完成后,如果雙向鏈表非空,調(diào)用 ensureHostCallbackIsScheduled 等待下次重繪后執(zhí)行剩余的任務(wù)節(jié)點(diǎn)。特別的,當(dāng)雙向鏈表中還存在 ImmediatePriority 優(yōu)先級(jí)的任務(wù)節(jié)點(diǎn),flushWork 將調(diào)用 flushImmediateWork 批量執(zhí)行這些任務(wù)節(jié)點(diǎn)。
function flushWork(didTimeout) { // Exit right away if we"re currently paused // 暫停情況下 直接退出 if (enableSchedulerDebugging && isSchedulerPaused) { return; } isExecutingCallback = true; var previousDidTimeout = currentDidTimeout; currentDidTimeout = didTimeout; try { // 如果已經(jīng)超時(shí) if (didTimeout) { // Flush all the expired callbacks without yielding. // 讓firstCallbackNode 雙向鏈表去消耗 while (firstCallbackNode !== null && !(enableSchedulerDebugging && isSchedulerPaused)) { // TODO Wrap in feature flag // Read the current time. Flush all the callbacks that expire at or // earlier than that time. Then read the current time again and repeat. // This optimizes for as few performance.now calls as possible. var currentTime = exports.unstable_now(); // 已經(jīng)過期的 直接執(zhí)行 if (firstCallbackNode.expirationTime <= currentTime) { do { flushFirstCallback(); } while (firstCallbackNode !== null && firstCallbackNode.expirationTime <= currentTime && !(enableSchedulerDebugging && isSchedulerPaused)); continue; } break; } } else { // Keep flushing callbacks until we run out of time in the frame. if (firstCallbackNode !== null) { do { if (enableSchedulerDebugging && isSchedulerPaused) { break; } flushFirstCallback(); // 沒有超時(shí) 也不用放入下一幀的的直接執(zhí)行 } while (firstCallbackNode !== null && !shouldYieldToHost()); } } } finally { isExecutingCallback = false; currentDidTimeout = previousDidTimeout; // 沒有處理玩的繼續(xù)的執(zhí)行 if (firstCallbackNode !== null) { // There"s still work remaining. Request another callback. ensureHostCallbackIsScheduled(); } else { isHostCallbackScheduled = false; } // Before exiting, flush all the immediate work that was scheduled. // 退出之前將所有立即執(zhí)行的任務(wù)去執(zhí)行 flushImmediateWork(); } }
因?yàn)?scheduler 使用首個(gè)任務(wù)節(jié)點(diǎn)的超時(shí)時(shí)間點(diǎn)作為 requestHostCallback 函數(shù)的次參(在 ensureHostCallbackIsScheduled 函數(shù)中處理)。因此,如果首個(gè)任務(wù)節(jié)點(diǎn)的優(yōu)先級(jí)為 ImmediatePriority,flushWork 所獲得參數(shù) didTimeout 也將是否值,其執(zhí)行邏輯將是執(zhí)行所有優(yōu)先級(jí)為 ImmediatePriority 的任務(wù)節(jié)點(diǎn),再調(diào)用 ensureHostCallbackIsScheduled 等待下一次重繪時(shí)執(zhí)行其余任務(wù)節(jié)點(diǎn)。如果首個(gè)任務(wù)節(jié)點(diǎn)的優(yōu)先級(jí)為 UserBlockingPriority 等,flushWork 將執(zhí)行同優(yōu)先級(jí)的任務(wù)節(jié)點(diǎn),再調(diào)用 ensureHostCallbackIsScheduled 等待下一次重繪時(shí)執(zhí)行其余任務(wù)節(jié)點(diǎn)。所有對(duì)不同優(yōu)先級(jí)的任務(wù)節(jié)點(diǎn),scheduler 采用分段執(zhí)行的策略8、 其他API
unstable_runWithPriority
function unstable_runWithPriority(priorityLevel, eventHandler) { switch (priorityLevel) { case ImmediatePriority: case UserBlockingPriority: case NormalPriority: case LowPriority: case IdlePriority: break; default: priorityLevel = NormalPriority; } var previousPriorityLevel = currentPriorityLevel; var previousEventStartTime = currentEventStartTime; currentPriorityLevel = priorityLevel; currentEventStartTime = getCurrentTime(); try { return eventHandler(); } finally { currentPriorityLevel = previousPriorityLevel; currentEventStartTime = previousEventStartTime; // Before exiting, flush all the immediate work that was scheduled. flushImmediateWork(); } }
unstable_runWithPriority(priorityLevel, eventHandler) 將 currentPriorityLevel 緩存設(shè)置為 priorityLevel,隨后再執(zhí)行 eventHandler,最后調(diào)用 flushImmediateWork 函數(shù)執(zhí)行所有優(yōu)先級(jí)為 ImmediatePriority 的任務(wù)節(jié)點(diǎn),其余任務(wù)節(jié)點(diǎn)等待下次重繪后再執(zhí)行。可以設(shè)想,當(dāng) eventHandler 為 unstable_scheduleCallback 函數(shù)時(shí),將影響所添加任務(wù)節(jié)點(diǎn)的優(yōu)先級(jí),并立即執(zhí)行 ImmediatePriority 優(yōu)先級(jí)的任務(wù)。其實(shí)就是給執(zhí)行eventHandler 設(shè)置優(yōu)先級(jí)
unstable_wrapCallback
function unstable_wrapCallback(callback) { var parentPriorityLevel = currentPriorityLevel; return function () { // This is a fork of runWithPriority, inlined for performance. var previousPriorityLevel = currentPriorityLevel; var previousEventStartTime = currentEventStartTime; currentPriorityLevel = parentPriorityLevel; currentEventStartTime = exports.unstable_now(); try { return callback.apply(this, arguments); } finally { currentPriorityLevel = previousPriorityLevel; currentEventStartTime = previousEventStartTime; flushImmediateWork(); } }; }
unstable_wrapCallback(callback) 記錄當(dāng)前的優(yōu)先級(jí) currentPriorityLevel,返回函數(shù)處理效果如 unstable_runWithPriority,對(duì)于 callback 中新添加的任務(wù)節(jié)點(diǎn)將使用所記錄的 currentPriorityLevel 作為優(yōu)先級(jí)。9 其他
這里可以返回的是function 將作為新的節(jié)點(diǎn)去插入被調(diào)度
unstable_pauseExecution 通過將 isSchedulerPaused 置為 true,打斷 scheduler 處理任務(wù)節(jié)點(diǎn)。
unstable_continueExecution 取消打斷狀態(tài),使 scheduler 恢復(fù)處理任務(wù)節(jié)點(diǎn)。
unstable_getFirstCallbackNode 獲取雙向鏈表中的首個(gè)任務(wù)節(jié)點(diǎn)。
unstable_cancelCallback(callbackNode) 從雙向鏈表中移除指定任務(wù)節(jié)點(diǎn)。
unstable_getCurrentPriorityLevel 獲取當(dāng)前優(yōu)先級(jí) currentPriorityLevel 緩存。
unstable_shouldYield 是否需要被打斷。
unstable_now 獲取當(dāng)前時(shí)間。
10 總結(jié)讀完scheduler源碼 感覺還是挺復(fù)雜的 當(dāng)然收獲也是比較大的 尤其是對(duì)于瀏覽執(zhí)行機(jī)制有了更深入的認(rèn)識(shí) 尤其調(diào)度思路讓人影響時(shí)刻, 當(dāng)然分析肯定會(huì)有不全面或者偏差的地方 歡迎大佬們指正
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/102524.html
摘要:標(biāo)簽源碼閱讀在中虛擬機(jī)的創(chuàng)建無疑是非常重要的了解虛擬機(jī)創(chuàng)建流程并閱讀模塊關(guān)于創(chuàng)建虛擬機(jī)的源碼對(duì)開發(fā)有很很大幫助本篇文章將以版本為基礎(chǔ)講解創(chuàng)建虛擬機(jī)的源碼由于模塊代碼復(fù)雜而且閱讀源碼所需知識(shí)較多所以側(cè)重于流程邏輯源碼閱讀可能不夠詳盡指出模塊結(jié) 標(biāo)簽: openstack nova 源碼閱讀 在openstack中,虛擬機(jī)的創(chuàng)建無疑是非常重要的,了解虛擬機(jī)創(chuàng)建流程并閱讀nova模塊關(guān)于創(chuàng)...
摘要:在使用過程中,我們可以使用注解可以方便的實(shí)現(xiàn)定時(shí)任務(wù)。默認(rèn)單線程經(jīng)排查后發(fā)現(xiàn),我們使用注解默認(rèn)的配置的話,所有的任務(wù)都是單線程去跑的。參數(shù)其實(shí)代表了每個(gè)路由的最大連接數(shù)。怪不得當(dāng)時(shí)在高并發(fā)情況下總會(huì)出現(xiàn)超時(shí),明明已經(jīng)設(shè)的很高。 前言 本文主要給大家介紹了關(guān)于Spring中@Scheduled和HttpClient的坑,分享出來供大家參考學(xué)習(xí),下面話不多說了,來一起看看詳細(xì)的介紹吧。 曾...
摘要:包主要實(shí)現(xiàn)類,這是一個(gè)抽象類,實(shí)現(xiàn)了通用的模板方法,并在方法內(nèi)部判斷錯(cuò)誤重試去重處理等。重置重復(fù)檢查就是清空,獲取請(qǐng)求總數(shù)也就是獲取的。至于請(qǐng)求總數(shù)統(tǒng)計(jì),就是返回中維護(hù)的的大小。 Scheduler是Webmagic中的url調(diào)度器,負(fù)責(zé)從Spider處理收集(push)需要抓取的url(Page的targetRequests)、并poll出將要被處理的url給Spider,同時(shí)還負(fù)責(zé)...
摘要:系列文章源碼分析第一篇源碼分析第二篇同步模式源碼分析第三篇異步狀態(tài)源碼分析第四篇?dú)w納總結(jié)前言是在版本中的大更新,利用了閑余時(shí)間看了一些源碼,做個(gè)小記錄流程圖源碼分析調(diào)用時(shí),會(huì)調(diào)用的方法,同時(shí)將新的作為參數(shù)傳進(jìn)會(huì)先調(diào)用獲取一個(gè)維護(hù)兩個(gè)時(shí)間一個(gè) 系列文章 React Fiber源碼分析 第一篇 React Fiber源碼分析 第二篇(同步模式) React Fiber源碼分析 第三篇(...
閱讀 1866·2019-08-29 16:44
閱讀 2171·2019-08-29 16:30
閱讀 779·2019-08-29 15:12
閱讀 3530·2019-08-26 10:48
閱讀 2658·2019-08-23 18:33
閱讀 3777·2019-08-23 17:01
閱讀 1943·2019-08-23 15:54
閱讀 1301·2019-08-23 15:05