亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

scheduler 源碼

SillyMonkey / 2005人閱讀

摘要:布爾型,表示該幀里面沒有執(zhí)行回調(diào),超時了。這一處理機制在監(jiān)聽函數(shù)中實現(xiàn)作為,接受消息的時機將隨著線程的空閑程度起變化。

為什么是要有scheduler
首先要從js的是單線程模型來說起,Javascript執(zhí)行是會經(jīng)歷靜態(tài)編譯,動態(tài)解釋和事件循環(huán)做任務調(diào)度的過程,大致的流程如下(注意,該流程是以chrome瀏覽器內(nèi)核為標準的執(zhí)行流程,在node或者其他瀏覽器中,執(zhí)行流程會有所差異,但是核心思想是差不多的。從這里面我們很直觀的認識到js的線程模型是怎么工作的。那跟scheduler有什么關系呢,我們都知道React16采用了fiber架構(gòu),這樣的架構(gòu)下,使用鏈表結(jié)構(gòu)代替原有的函數(shù)嵌套,避免無法控制組件渲染的過程的問題,F(xiàn)iber讓React內(nèi)部會動態(tài)靈活的管理所有組件的渲染任務,可以中斷暫停某一個組件的渲染,所以,對于復雜型應用來說,對于某一個交互動作的反饋型任務,我們是可以對其進行拆解,一步步的做交互反饋,避免在一個頁面重繪時間周期內(nèi)做過多的事情,這樣就能減少應用的長任務,最大化提升應用操作性能,更好的利用有限的時間,那么,我們現(xiàn)在可以只聚焦在任務管理上,一起來研究一下React到底是如何調(diào)度組件的任務執(zhí)行的,這里說渲染感覺不太準確

macrotask: setTimeout, setInterval, setImmediate,MessageChannel, I/O, UI rendering netWork


microtask: process.nextTick, Promises, Object.observe(廢棄), MutationObserver

[引自大神論述]



上圖來自知乎文章

主線程負責解析編譯和調(diào)度異步事件循環(huán)調(diào)度

異步隊列和V8通訊 通過polling Check來實現(xiàn)

異步隊列分成macotask 和 microtask

macrotask
一般情況下,在沒有特別說明的情況下我們會把macrotask稱為task queues ,在一次的事件循環(huán)中,他只會執(zhí)行一次
microtask

在每一次事件循環(huán)中,macrotask 只會提取一個執(zhí)行,而 microtask 會一直提取,直到 microtasks 隊列清空。

那么也就是說如果我的某個 microtask 任務又推入了一個任務進入 microtasks 隊列,那么在主線程完成該任務之后,仍然會繼續(xù)運行 microtasks 任務直到任務隊列耗盡

而事件循環(huán)每次只會入棧一個 macrotask ,主線程執(zhí)行完該任務后又會先檢查 microtasks 隊列并完成里面的所有任務后再執(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ù)上述理論自己試試程序的運行結(jié)果, 為什么我們在分析scheduler源碼之前先要介紹下異步隊列,因為了解清楚js異步隊列才會讓我們更加清晰知道scheduler是怎么使用調(diào)度方法來更好的安排代碼執(zhí)行時機。

瀏覽器的執(zhí)行頁面繪制過程



圖片出自同一地方
執(zhí)行JS(具體流程在上面有描述)--->計算Style--->構(gòu)建布局模型(Layout)--->繪制圖層樣式(Paint)--->組合計算渲染呈現(xiàn)結(jié)果(Composite)

一個完成的過程稱之為一幀

一般設備的刷新頻率是60Hz (還有更大的 scheduler 最大設定為 120hz) 也就是按照理想情況來說一秒鐘有60幀 那么一幀的平均時間是 1000 / 60 大約是 16.7ms 也就是我們一幀的執(zhí)行時間不能超過 16.7 否則就會出現(xiàn)丟失幀和卡頓情況

幀的渲染是在處理完流程之后進行的

幀的渲染是在獨立的UI線程去執(zhí)行 是有GPU等

剩余的時間為空閑時間

在離散型 交互動作中不一定要求需要16.7 ms的時間

對于離散型交互,上一幀的渲染到下一幀的渲染時間是屬于系統(tǒng)空閑時間,經(jīng)過親測,Input輸入,最快的單字符輸入時間平均是33ms(通過持續(xù)按同一個鍵來觸發(fā)),相當于,上一幀到下一幀中間會存在大于16.4ms的空閑時間,就是說任何離散型交互,最小的系統(tǒng)空閑時間也有16.4ms,也就是說,離散型交互的最短幀長一般是33ms

requestIdleCallback
在幀的渲染中當執(zhí)行完流程和UI繪制之后 會有一部分空閑時間,如果我們能掌握這個時間加一充分利用就更加理想
那如何知道一幀進入這個空閑時間呢,瀏覽器目前提供了這個回調(diào) requestIdleCallback 即瀏覽器空閑時
var handle = window.requestIdleCallback(callback[, options]);

requestIdleCallback回調(diào)調(diào)用時機是在回調(diào)注冊完成的上一幀渲染到下一幀渲染之間的空閑時間執(zhí)行

callback 是要執(zhí)行的回調(diào)函數(shù),會傳入 deadline 對象作為參數(shù),deadline 包含:

timeRemaining:剩余時間,單位 ms,指的是該幀剩余時間。

didTimeout:布爾型,true 表示該幀里面沒有執(zhí)行回調(diào),超時了。

option: {
timeout : 即超時時間, 不提供瀏覽器自己去計算

}

如果給定 timeout,那到了時間,不管有沒有剩余時間,都會立刻執(zhí)行回調(diào) callback。

requestAnimationFrame

以前我們知道: requestAnimationFrame回調(diào)只會在當前頁面激活狀態(tài)下執(zhí)行,可以大大節(jié)省CPU開銷

requestAnimationFrame回調(diào)參數(shù)是回調(diào)被調(diào)用的時間,也就是當前幀的起始時間(可以通過這個時間去判斷 到底有么有超時)

系統(tǒng)控制回調(diào)的執(zhí)行時機恰好在回調(diào)注冊完成后的下一幀渲染周期的起點的開始執(zhí)行,控制js計算的到屏幕響應的精確性,避免步調(diào)不一致而導致丟幀

目前瀏覽器對于requestIdleCallback的支持不是特別完整,所以react團隊放棄了requestIdleCallback的使用
自己用requestAnimationFrame和MessageChannel來polyfill

requestIdleCallback Polyfill方案

很簡單,33毫秒,但是時間并不總是33ms,這個時間是React認為的一個可以接受的最大值,如果運行設備能做到大于30fps,那么它會去調(diào)整這個值(通常情況下可以調(diào)整到16.6ms)。調(diào)整策略是用當前每幀的總時間與實際每幀的時間進行比較,當實際時間小于當前時間且穩(wěn)定(前后兩次都小于當前時間),那么就會認為這個值是有效的,然后將每幀時間調(diào)整為該值(取前后兩次中時間大的值),還有就是requestAnimationFrame回調(diào)的第一個參數(shù),每一幀的起始時間,最終借助requestAnimationFrame讓一批扁平的任務恰好控制在一塊一塊的33ms這樣的時間片內(nèi)執(zhí)行即可

所有準備工作都做好了, 接下來我們逐步來分析Scheduler源碼

1、 調(diào)度基本常量定義
// 枚舉

// 立即執(zhí)行的任務
var ImmediatePriority = 1;

// 用戶阻塞優(yōu)先級
var UserBlockingPriority = 2;

// 一般的優(yōu)先級
var NormalPriority = 3;

// 低級的優(yōu)先級
var LowPriority = 4;

// 空閑的優(yōu)先級
var IdlePriority = 5;

// 我們可以理解 優(yōu)先級越高 過期時間就越短 反之 越長

// 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
// 超時的優(yōu)先級時間 說明沒有剩余時間了 需要立即被調(diào)度
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
// Eventually times out
// 事件的過期時間 250 ms
var USER_BLOCKING_PRIORITY = 250;

// 一般優(yōu)先級的過期時間 5000 ms
var NORMAL_PRIORITY_TIMEOUT = 5000;

// 優(yōu)先級低的 10000ms
var LOW_PRIORITY_TIMEOUT = 10000;

// Never times out 空閑的任務 有沒有限制了 也就是最大整數(shù)
var IDLE_PRIORITY = maxSigned31BitInt;
2、 調(diào)度所需變量的定義
// Callbacks are stored as a circular, doubly linked list.
// 回調(diào)保存為了循環(huán)的雙向鏈表
var firstCallbackNode = null;

// 當前是否過期
var currentDidTimeout = false;
// Pausing the scheduler is useful for debugging.

// 調(diào)度是否中斷
var isSchedulerPaused = false;

// 默認當前的優(yōu)先級為一般優(yōu)先級 
var currentPriorityLevel = NormalPriority;

// 當前時間開始時間
var currentEventStartTime = -1;

// 當前過期時間
var currentExpirationTime = -1;

// This is set when a callback is being executed, to prevent re-entrancy.
// 當前是否執(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) {

 //開始時間
  var startTime = currentEventStartTime !== -1 ? currentEventStartTime : exports.unstable_now();

  var expirationTime;
  
  // 過期時間 這里模擬的是 requestIdleCallback options的 timeout的定義
  // 如果這里指定了 timeout 就會計算出 過期時間
 // 如果么有指定就會根據(jù) 調(diào)度程序的優(yōu)先級去計算  比如 普通是 5000 低級是 10000 空閑就永遠不會過期等....
  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;
    }
  }

  // 新建一個節(jié)點
  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都擁有相同的過期時間
  
  // 如果鏈表是空的 則 重新構(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ù)過期時間進行排序 遍歷查找 尋找比當前過期時間大的節(jié)點
    do {
      if (node.expirationTime > expirationTime) {
        // The new callback expires before this one.
        next = node;
        break;
      }
      node = node.next;
    } while (node !== firstCallbackNode);
 
    // 沒有找到比當前更靠后的 元素 說明當前的節(jié)點是最不優(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.
      // 當前新加入的節(jié)點是最后面的 指針指向鏈表頭
      next = firstCallbackNode;
    } else if (next === firstCallbackNode) {
      // The new callback has the earliest expiration in the entire list.
      
      // 說明所有的任務
      firstCallbackNode = newNode;
      ensureHostCallbackIsScheduled();
    }


    //將新的node 加入到鏈表 維護一下循環(huán)鏈表
    var previous = next.previous;
    previous.next = next.previous = newNode;
    newNode.next = next;
    newNode.previous = previous;
  }

  return newNode;
}
4. ensureHostCallbackIsScheduled 遇到優(yōu)先級高的 需要特別處理
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)先級最高的 進行調(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)頁選項卡如果在未激活狀態(tài)下requestAnimationFrame不會被觸發(fā)的問題,
*這樣的話,調(diào)度器是可以在后臺繼續(xù)做調(diào)度的,一方面也能提升用戶體驗,
* 同時后臺執(zhí)行的時間間隔是以100ms為步長,這個是一個最佳實踐,100ms是不會影響用戶體驗同時也不影響CPU能耗的一個折中時間間隔
為什么要用 settimeout 因為requestAnimationFrame不會在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的方式 這里不做主要分析 因為
比較簡單,這里將主要研究現(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.
  // 動態(tài)注入 用于測試目的
  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隊列 這里應該是下下策 
    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ā)送中標識
  var isMessageEventScheduled = false;
  // 過期時間
  var timeoutTime = -1;
  // rAF 輪詢啟動狀態(tài)
  var isAnimationFrameScheduled = false;
  
  // 任務執(zhí)行中標識
  var isFlushingHostCallback = false;
  // 下一幀期望完成時間點,用于判斷重繪后 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.
  /**
  * 
   我們假設我們以30fps運行,然后進行啟發(fā)式跟蹤
   如果我們獲得更頻繁的動畫,我會將此值調(diào)整為更快的fps
   幀
   默認33  為什么是33 因為我們假定每秒30幀固定評率刷新 也就是 一幀需要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;
  
  // 當port1 發(fā)送消息后 這里在幀重繪完成后 進入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;
    // 說明沒有時間了 當前幀給與這個callback的時間沒有了
    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.
      // 檢查當前callback 是否過期 和 是否被執(zhí)行
      // previousFrameTime 小于等于 currentTime 時,scheduler
      // 認為線程不是空閑的,對于超時的任務將立即執(zhí)行,
      // 對于未超時的任務將在下次重繪后予以處理
      // 顯然是超時的 并且沒有被取消 直接執(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.
        //沒有超時 如果沒有安排輪詢 就開啟輪詢
        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 這里應該是終點了 到這里為止 調(diào)度分析完了
    // 執(zhí)行完成的調(diào)度 返回的只有 是否已經(jīng)過期(didTimeout)
    if (prevScheduledCallback !== null) {
      isFlushingHostCallback = true;
      try {
        prevScheduledCallback(didTimeout);
      } finally {
        isFlushingHostCallback = false;
      }
    }
  };
 
  // 這里作為rAF的callback 處理函數(shù)
  /**
  * 在 animateTick 中,scheduler 將計算下一幀期望完成時間點 previousFrameTime,
  然后通過 port.postMessage 方法發(fā)送消息。等到 port1 接受到消息時,schdulear
  將 previousFrameTime 與 currentTime 作比較:當 previousFrameTime 小于等于 currentTime 時,
  scheduler 認為線程不是空閑的,對于超時的任務將立即執(zhí)行,對于未超時的任務將在下次重繪后予以處理;
  當 previousFrameTime 大于 currentTime 時,線程就是空閑的,scheduler 將立即執(zhí)行。這一處理機制在
  port1.onMessage 監(jiān)聽函數(shù)中實現(xiàn)(作為 macrotasks,port1 接受消息的時機將隨著線程的空閑程度起變化)。
  
  */
  var animationTick = function (rafTime) {
    //輪詢了 這里進入
    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.
      /**
      * 
    * 最先在幀的開頭安排下一個回調(diào)。如果調(diào)度程序隊列在幀的末尾不為空,
    * 它將繼續(xù)在該回調(diào)內(nèi)刷新。如果隊列*為*空,則它將立即退出
   *。在幀的開頭觸發(fā)回調(diào)可確保在最早的幀內(nèi)觸發(fā)。要是我們
   *等到幀結(jié)束后觸發(fā)回調(diào),我們冒著瀏覽器丟幀的風險,
    *并且在此幀之后的不會觸發(fā)回調(diào)。
      */
      requestAnimationFrameWithTimeout(animationTick);
    } else {
      // No pending work. Exit.
      isAnimationFrameScheduled = false;
      return;
    }
   
    // 調(diào)度的時間rafTime - frameDeadline 下一幀預到期 + 一幀的多少  = 給下一幀留下的時間 
    var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
    
    // 幀的頻率小于當前的 說明處理的時間都是比較短的
    // 其實這里做了下調(diào)整 如果當前的設備的更新頻率大于我們設定的 30fps 
    // 我們就需要取更新的頻率的最大值 這里的最大值的更新頻率 最大值 
    // 我們需要澄清一個問題 頻率越大 一幀花費的時間就越短 
    if (nextFrameTime < activeFrameTime && previousFrameTime < activeFrameTime) {
      if (nextFrameTime < 8) {
        // 預防性代碼  也就是說 不支持刷新頻率大于120hz 如果大于120 就當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.
      
      
     //如果一幀長,那么下一幀可能很短。
    //  如果兩個幀連續(xù)短,那么這表明我們實際上具有比我們當前優(yōu)化
    //的幀速率更高的幀速率。我們相應地動態(tài)調(diào)整啟發(fā)式。例如,如果我們是
     // 在120hz顯示屏或90hz VR顯示屏上運行。
      // 取兩個中的最大值,以防其中一個因錯過幀截止日期而異常。
      activeFrameTime = nextFrameTime < previousFrameTime ? previousFrameTime : nextFrameTime;
      
      
    } else {
      previousFrameTime = nextFrameTime;
    }
    
    // 計算下一幀過期時間
    frameDeadline = rafTime + activeFrameTime;
    // 如果還么發(fā)送消息 就觸發(fā)message  讓幀重繪完成后 進行調(diào)度callback
    if (!isMessageEventScheduled) {
      isMessageEventScheduled = true;
      port.postMessage(undefined);
    }
  };


  // 
  requestHostCallback = function (callback, absoluteTimeout) {
    // 設置當前的callback
    scheduledHostCallback = callback;
    // 設置過期時間
    timeoutTime = absoluteTimeout;
    //當前如果有任務正在執(zhí)行中(意為當前沒有重繪任務,重繪線程是空閑的)
    // 或者所添加的任務需要立即執(zhí)行,scheduler 直接調(diào)用 port.postMessage 發(fā)送消息,跳過 rAF 
    // 輪詢,以使任務得到即時執(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 沒有進行調(diào)度 安排一個新的 rAF輪詢
      // 如果rAF 沒有發(fā)揮作用 在使用settimeout 去作為預備去調(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 輪詢未啟動,調(diào)用 requestAnimationFrameWithTimeout(animationTick) 啟動輪詢
      requestAnimationFrameWithTimeout(animationTick);
    }
  };

  cancelHostCallback = function () {
    scheduledHostCallback = null;
    isMessageEventScheduled = false;
    timeoutTime = -1;
  };
}
7 執(zhí)行到調(diào)度程序 callback 這里callback是怎么執(zhí)行的(flushWork)

flushFirstCallback

flushFirstCallback 從雙向鏈表中取出首個任務節(jié)點并執(zhí)行。若首個任務節(jié)點的 callback 返回函數(shù),使用該函數(shù)構(gòu)建新的 callbackNode 任務節(jié)點,并將該任務節(jié)點插入雙向鏈表中:若該任務節(jié)點的優(yōu)先級最高、且不只包含一個任務節(jié)點,調(diào)用 ensureHostCallbackIsScheduled,在下一次重繪后酌情執(zhí)行雙向鏈表中的任務節(jié)點;否則只將新創(chuàng)建的任務節(jié)點添加到雙向鏈表中
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 {
    // 恢復當一次的優(yōu)先級
    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)度
  // 跟新加入一個節(jié)點是一樣的 就不在分析了
  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)先級為 ImmediatePriority 的任務節(jié)點。如果雙向鏈表不只包含優(yōu)先級為 ImmediatePriority 的任務節(jié)點,flushImmediateWork 將調(diào)用 ensureHostCallbackIsScheduled 等待下次重繪后執(zhí)行剩余的任務節(jié)點。
function flushImmediateWork() {
  if (
  // Confirm we"ve exited the outer most event handler
  // 確認我們退出了最外層的事件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)先級的  依次輪詢調(diào)度
      if (firstCallbackNode !== null) {
        // There"s still work remaining. Request another callback.
        ensureHostCallbackIsScheduled();
      } else {
        isHostCallbackScheduled = false;
      }
    }
  }
}

flushWork

flushWork 作為 requestHostCallback 函數(shù)的參數(shù),獲得的首個實參 didTimeout 為是否超時的標識。如果超時,flushWork 通過調(diào)用 flushFirstCallback 批量執(zhí)行所有未超時的任務節(jié)點;若果沒有超時,flushWork 將在下一幀未完成前(通過 shouldYieldToHost 函數(shù)判斷)盡可能地執(zhí)行任務節(jié)點。等上述條件邏輯執(zhí)行完成后,如果雙向鏈表非空,調(diào)用 ensureHostCallbackIsScheduled 等待下次重繪后執(zhí)行剩余的任務節(jié)點。特別的,當雙向鏈表中還存在 ImmediatePriority 優(yōu)先級的任務節(jié)點,flushWork 將調(diào)用 flushImmediateWork 批量執(zhí)行這些任務節(jié)點。
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)超時
    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();
          // 沒有超時 也不用放入下一幀的的直接執(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í)行的任務去執(zhí)行
    flushImmediateWork();
  }
}
因為 scheduler 使用首個任務節(jié)點的超時時間點作為 requestHostCallback 函數(shù)的次參(在 ensureHostCallbackIsScheduled 函數(shù)中處理)。因此,如果首個任務節(jié)點的優(yōu)先級為 ImmediatePriority,flushWork 所獲得參數(shù) didTimeout 也將是否值,其執(zhí)行邏輯將是執(zhí)行所有優(yōu)先級為 ImmediatePriority 的任務節(jié)點,再調(diào)用 ensureHostCallbackIsScheduled 等待下一次重繪時執(zhí)行其余任務節(jié)點。如果首個任務節(jié)點的優(yōu)先級為 UserBlockingPriority 等,flushWork 將執(zhí)行同優(yōu)先級的任務節(jié)點,再調(diào)用 ensureHostCallbackIsScheduled 等待下一次重繪時執(zhí)行其余任務節(jié)點。所有對不同優(yōu)先級的任務節(jié)點,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 緩存設置為 priorityLevel,隨后再執(zhí)行 eventHandler,最后調(diào)用 flushImmediateWork 函數(shù)執(zhí)行所有優(yōu)先級為 ImmediatePriority 的任務節(jié)點,其余任務節(jié)點等待下次重繪后再執(zhí)行??梢栽O想,當 eventHandler 為 unstable_scheduleCallback 函數(shù)時,將影響所添加任務節(jié)點的優(yōu)先級,并立即執(zhí)行 ImmediatePriority 優(yōu)先級的任務。其實就是給執(zhí)行eventHandler 設置優(yōu)先級

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) 記錄當前的優(yōu)先級 currentPriorityLevel,返回函數(shù)處理效果如 unstable_runWithPriority,對于 callback 中新添加的任務節(jié)點將使用所記錄的 currentPriorityLevel 作為優(yōu)先級。
這里可以返回的是function 將作為新的節(jié)點去插入被調(diào)度
9 其他

unstable_pauseExecution 通過將 isSchedulerPaused 置為 true,打斷 scheduler 處理任務節(jié)點。

unstable_continueExecution 取消打斷狀態(tài),使 scheduler 恢復處理任務節(jié)點。

unstable_getFirstCallbackNode 獲取雙向鏈表中的首個任務節(jié)點。

unstable_cancelCallback(callbackNode) 從雙向鏈表中移除指定任務節(jié)點。

unstable_getCurrentPriorityLevel 獲取當前優(yōu)先級 currentPriorityLevel 緩存。

unstable_shouldYield 是否需要被打斷。

unstable_now 獲取當前時間。

10 總結(jié)

讀完scheduler源碼 感覺還是挺復雜的 當然收獲也是比較大的 尤其是對于瀏覽執(zhí)行機制有了更深入的認識 尤其調(diào)度思路讓人影響時刻, 當然分析肯定會有不全面或者偏差的地方 歡迎大佬們指正

文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/102524.html

相關文章

  • openstack創(chuàng)建虛擬機源碼閱讀

    摘要:標簽源碼閱讀在中虛擬機的創(chuàng)建無疑是非常重要的了解虛擬機創(chuàng)建流程并閱讀模塊關于創(chuàng)建虛擬機的源碼對開發(fā)有很很大幫助本篇文章將以版本為基礎講解創(chuàng)建虛擬機的源碼由于模塊代碼復雜而且閱讀源碼所需知識較多所以側(cè)重于流程邏輯源碼閱讀可能不夠詳盡指出模塊結(jié) 標簽: openstack nova 源碼閱讀 在openstack中,虛擬機的創(chuàng)建無疑是非常重要的,了解虛擬機創(chuàng)建流程并閱讀nova模塊關于創(chuàng)...

    muddyway 評論0 收藏0
  • Spring中@Scheduled和HttpClient的連環(huán)坑

    摘要:在使用過程中,我們可以使用注解可以方便的實現(xiàn)定時任務。默認單線程經(jīng)排查后發(fā)現(xiàn),我們使用注解默認的配置的話,所有的任務都是單線程去跑的。參數(shù)其實代表了每個路由的最大連接數(shù)。怪不得當時在高并發(fā)情況下總會出現(xiàn)超時,明明已經(jīng)設的很高。 前言 本文主要給大家介紹了關于Spring中@Scheduled和HttpClient的坑,分享出來供大家參考學習,下面話不多說了,來一起看看詳細的介紹吧。 曾...

    趙連江 評論0 收藏0
  • 爬蟲框架WebMagic源碼分析之Scheduler

    摘要:包主要實現(xiàn)類,這是一個抽象類,實現(xiàn)了通用的模板方法,并在方法內(nèi)部判斷錯誤重試去重處理等。重置重復檢查就是清空,獲取請求總數(shù)也就是獲取的。至于請求總數(shù)統(tǒng)計,就是返回中維護的的大小。 Scheduler是Webmagic中的url調(diào)度器,負責從Spider處理收集(push)需要抓取的url(Page的targetRequests)、并poll出將要被處理的url給Spider,同時還負責...

    TIGERB 評論0 收藏0
  • React Fiber源碼分析 第三篇(異步狀態(tài))

    摘要:系列文章源碼分析第一篇源碼分析第二篇同步模式源碼分析第三篇異步狀態(tài)源碼分析第四篇歸納總結(jié)前言是在版本中的大更新,利用了閑余時間看了一些源碼,做個小記錄流程圖源碼分析調(diào)用時,會調(diào)用的方法,同時將新的作為參數(shù)傳進會先調(diào)用獲取一個維護兩個時間一個 系列文章 React Fiber源碼分析 第一篇 React Fiber源碼分析 第二篇(同步模式) React Fiber源碼分析 第三篇(...

    worldligang 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<