react

react浅析3 任务调度

2020-09-09  本文已影响0人  百里哈哈

可以做一个简单的示例其代码如下

import Scheduler from 'scheduler'
let NormalPriority = Scheduler.unstable_NormalPriority;
let ImmediatePriority = Scheduler.unstable_ImmediatePriority;
let LowPriority = Scheduler.unstable_LowPriority;
let scheduleCallback = Scheduler.unstable_scheduleCallback;

function test1() {
    scheduleCallback(NormalPriority, () => {
        console.log('A')
    })
    scheduleCallback(ImmediatePriority, () => {
        console.log('B')
    })
    scheduleCallback(LowPriority, () => {
        console.log('C')
    })
    scheduleCallback(NormalPriority, () => {
        console.log('D')
    })
    // console.log('out log tag')
}
test1();

通过scheduleCallback放入需要调度的任务, 我们对任务的优先级分别进行NormalPriority、ImmediatePriority、NormalPriority
可以看到其输出结果为BADC

Scheduler.png

unstable_scheduleCallback

  1. 创建一个task任务, 包括计算设置它的截止时间(expirationTime)
  2. 如果当前任务是一个延时任务,
    a. 将新任务放入timerQueue(延时队列)中
    b. 正在执行的任务队列为空且该任务为待执行中最早的,则延时requestHostCallback的调用
  3. 如果当前任务是非延时异步任务,则将其放入taskQueue,调用requestHostCallback方法
function unstable_scheduleCallback(priorityLevel, callback, options) {
  var currentTime = exports.unstable_now();
  var startTime;
  var timeout;

  if (typeof options === 'object' && options !== null) {
    var delay = options.delay;

    if (typeof delay === 'number' && delay > 0) {
      startTime = currentTime + delay;
    } else {
      startTime = currentTime;
    }

    timeout = typeof options.timeout === 'number' ? options.timeout : timeoutForPriorityLevel(priorityLevel);
  } else {
    timeout = timeoutForPriorityLevel(priorityLevel);
    startTime = currentTime;
  }

  var expirationTime = startTime + timeout;
  var newTask = {
    id: taskIdCounter++,
    callback: callback,
    priorityLevel: priorityLevel,
    startTime: startTime,
    expirationTime: expirationTime,
    sortIndex: -1
  };

  {
    newTask.isQueued = false;
  }

  if (startTime > currentTime) {
    // This is a delayed task.
    newTask.sortIndex = startTime;
    push(timerQueue, newTask);

    if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
      // All tasks are delayed, and this is the task with the earliest delay.
      if (isHostTimeoutScheduled) {
        // Cancel an existing timeout.
        cancelHostTimeout();
      } else {
        isHostTimeoutScheduled = true;
      } // Schedule a timeout.


      requestHostTimeout(handleTimeout, startTime - currentTime);
    }
  } else {
    newTask.sortIndex = expirationTime;
    push(taskQueue, newTask);

    {
      markTaskStart(newTask, currentTime);
      newTask.isQueued = true;
    } // Schedule a host callback, if needed. If we're already performing work,
    // wait until the next time we yield.


    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback(flushWork);
    }
  }

  return newTask;
}
 requestHostTimeout = function (cb, ms) {
    _timeoutID = setTimeout(cb, ms);
  };

requestHostCallback

如果是支持MessageChannel,requestHostCallback则采用postMessage异步进行实现。
不支持的使用setTimout来实现。

var channel = new MessageChannel();
var port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
requestHostCallback = function (callback) {
    scheduledHostCallback = callback;

    if (!isMessageLoopRunning) {
      isMessageLoopRunning = true;
      port.postMessage(null);
    }
  };

补充一个浏览器异步事件测试用例, 其结果为 MutationObserver Promise postMessage setTimeout.
如果MutationObserver与Promise测试调换的话其结果为 Promise MutationObserver postMessage setTimeout, 说明二者优先级相同。

function test2() {
  setTimeout(function() {
    console.log('setTimeout')
  }, 0)
  const channel = new MessageChannel();
  const port = channel.port2;
  channel.port1.onmessage = function () {
      console.log('postMessage')
  };
  port.postMessage(null);

  const targetNode = document.createElement('div');
  const config = { attributes: true, childList: true, subtree: true };
  const callback = function () {
      console.log('MutationObserver')
  };
  const observer = new MutationObserver(callback);
  observer.observe(targetNode, config);
  targetNode.setAttribute('id', 'item');

  const promise = new Promise(function (resovle, reject) {
    resovle('Promise');
  })
  promise.then(function(val) {
    console.log(val)
  }, function() {});
}

performWorkUntilDeadline

有代码可知, 如果scheduledHostCallback(requestHostCallback方法中,会将它赋值为flushWork)存在时,则调用该方法。
a. 如果在浏览器的空余时间片结束之后, 还有任务需要执行则通过postMessage推入到异步中等待下一次空余时间执行
b. 如果在执行中捕获到错误,依旧通过postMessage推入到异步中等待下一次空余时间执行。

var performWorkUntilDeadline = function () {
    if (scheduledHostCallback !== null) {
      var currentTime = exports.unstable_now(); // Yield after `yieldInterval` ms, regardless of where we are in the vsync
      // cycle. This means there's always time remaining at the beginning of
      // the message event.

      deadline = currentTime + yieldInterval;
      var hasTimeRemaining = true;

      try {
        var hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);

        if (!hasMoreWork) {
          isMessageLoopRunning = false;
          scheduledHostCallback = null;
        } else {
          // If there's more work, schedule the next message event at the end
          // of the preceding one.
          port.postMessage(null);
        }
      } catch (error) {
        // If a scheduler task throws, exit the current browser task so the
        // error can be observed.
        port.postMessage(null);
        throw error;
      }
    } else {
      isMessageLoopRunning = false;
    } // Yielding to the browser will give it a chance to paint, so we can
  };

flushWork

该方法主要是对workLoop进行调用

function flushWork(hasTimeRemaining, initialTime) {
  isHostCallbackScheduled = false;

  if (isHostTimeoutScheduled) {
    // We scheduled a timeout but it's no longer needed. Cancel it.
    isHostTimeoutScheduled = false;
    cancelHostTimeout();
  }

  isPerformingWork = true;
  var previousPriorityLevel = currentPriorityLevel;

  try {
    if (enableProfiling) {
      try {
        return workLoop(hasTimeRemaining, initialTime);
      } catch (error) {
        if (currentTask !== null) {
          var currentTime = exports.unstable_now();
          markTaskErrored(currentTask, currentTime);
          currentTask.isQueued = false;
        }

        throw error;
      }
    } else {
      // No catch in prod codepath.
      return workLoop(hasTimeRemaining, initialTime);
    }
  } finally {
    currentTask = null;
    currentPriorityLevel = previousPriorityLevel;
    isPerformingWork = false;

    {
      var _currentTime = exports.unstable_now();

      markSchedulerSuspended(_currentTime);
    }
  }
}

workLoop

有代码可知,通过该方法执行task任务,其返回值为是否还有待执行的task

function workLoop(hasTimeRemaining, initialTime) {
  var currentTime = initialTime;
  advanceTimers(currentTime);
  currentTask = peek(taskQueue);

  while (currentTask !== null && !(enableSchedulerDebugging )) {
    if (currentTask.expirationTime > currentTime && (!hasTimeRemaining || shouldYieldToHost())) {
      // This currentTask hasn't expired, and we've reached the deadline.
      break;
    }

    var callback = currentTask.callback;

    if (callback !== null) {
      currentTask.callback = null;
      currentPriorityLevel = currentTask.priorityLevel;
      var didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
      markTaskRun(currentTask, currentTime);
      var continuationCallback = callback(didUserCallbackTimeout);
      currentTime = exports.unstable_now();

      if (typeof continuationCallback === 'function') {
        currentTask.callback = continuationCallback;
        markTaskYield(currentTask, currentTime);
      } else {
        {
          markTaskCompleted(currentTask, currentTime);
          currentTask.isQueued = false;
        }

        if (currentTask === peek(taskQueue)) {
          pop(taskQueue);
        }
      }

      advanceTimers(currentTime);
    } else {
      pop(taskQueue);
    }

    currentTask = peek(taskQueue);
  } // Return whether there's additional work


  if (currentTask !== null) {
    return true;
  } else {
    var firstTimer = peek(timerQueue);

    if (firstTimer !== null) {
      requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
    }

    return false;
  }
}

react-dom中进入任务调度的入口方法 scheduleCallback

function scheduleCallback(reactPriorityLevel, callback, options) {
  var priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
  return Scheduler_scheduleCallback(priorityLevel, callback, options);
}

unstable_cancelCallback

unstable_cancelCallback进行任务取消, 它并不会进行删除而是将isQueued值为false

function unstable_cancelCallback(task) {
  {
    if (task.isQueued) {
      var currentTime = exports.unstable_now();
      markTaskCanceled(task, currentTime);
      task.isQueued = false;
    }
  } // Null out the callback to indicate the task has been canceled. (Can't
  // remove from the queue because you can't remove arbitrary nodes from an
  // array based heap, only the first one.)
上一篇下一篇

猜你喜欢

热点阅读