美文网首页react
react浅析3 任务调度

react浅析3 任务调度

作者: 百里哈哈 | 来源:发表于2020-09-09 13:40 被阅读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.)
    

    相关文章

      网友评论

        本文标题:react浅析3 任务调度

        本文链接:https://www.haomeiwen.com/subject/kskdektx.html