react源码分析(4)-调度过程及原理

目录

react-reconciler与scheduler关系

scheduler介绍

JS中的偏函数

总结




2021SC@SDUSC

react-reconciler与scheduler关系

这里给出react-reconciler与scheduler的关系图

此图片的alt属性为空;文件名为Scheduler.png

关于react17的最新版本,我查阅多方资料,但由于在任务调度方面好像改动不小,所以不得不自己参考源码,最终找到了react与scheduler的连通入口,在ReactFiberWorkLoop.new.js中的schedulerCallBack正是这样的方法,它来自于scheduler.js中。根据该方法找到scheduler.js的源码,接下来开始介绍sceduler。

scheduler介绍

作为调度模块,可以首先看一下任务优先级

此图片的alt属性为空;文件名为image-6.png

对应scheduler.js中的scheduler方法的是源码中的 unstable_scheduleCallback方法。

function unstable_scheduleCallback(priorityLevel, callback, options) {
  var currentTime = getCurrentTime();

  var startTime;
  if (typeof options === 'object' && options !== null) {
    var delay = options.delay;
    if (typeof delay === 'number' && delay > 0) {
      startTime = currentTime + delay;
    } else {
      startTime = currentTime;
    }
  } else {
    startTime = currentTime;
  }

  var timeout;
  switch (priorityLevel) {
    case ImmediatePriority:
      timeout = IMMEDIATE_PRIORITY_TIMEOUT;
      break;
    case UserBlockingPriority:
      timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
      break;
    case IdlePriority:
      timeout = IDLE_PRIORITY_TIMEOUT;
      break;
    case LowPriority:
      timeout = LOW_PRIORITY_TIMEOUT;
      break;
    case NormalPriority:
    default:
      timeout = NORMAL_PRIORITY_TIMEOUT;
      break;
  }

  var expirationTime = startTime + timeout;

  var newTask = {
    id: taskIdCounter++,
    callback,
    priorityLevel,
    startTime,
    expirationTime,
    sortIndex: -1,
  };
  if (enableProfiling) {
    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);
    if (enableProfiling) {
      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;
}

接下来对该方法进行分析,该方法需要传入优先级,(delay是options可选项中的属性,callback的开始时间就是当前时间+delay),然后根据优先级计算出timeout,根据不同的优先级,对应不同的timeout常量,而过期时间就是开始时间+timeout,接着进行比较,如果开始时间小于当前时间,就将其放入timerQueue中然后等待直到taskQueue中的任务执行完为止,通过handleTimeout设立等待时间,当等待时间满足就将其放入taskQueue中,(由图中代码可知,new task 的ID应当为taskIDcounter++)如果开始时间大于当前时间,就直接将其放入taskqueue中(按照过期时间的顺序进行排序)。当前无任务执行时就会执行requestHostCallback以对任务进行调度。在requestHostCallback中会将flushWork赋值给scheduledHostCallback,也就是说,执行任务的函数为flushWork。当前isMessageLoopRunning为false,也就是messageChannel尚未运行,那就执行schedulePerformWorkUntilDeadline,该函数向messageChannel的port2端口发送信息(此时performWorkUntilDeadline已经在port1处监听),所以执行 performWorkUntilDeadline方法,在performWorkUntilDeadline方法中会执行scheduledHostCallback,也就是flushWork,逐步执行完毕taskQueue中的任务。   

const performWorkUntilDeadline = () => {
  if (scheduledHostCallback !== null) {
    const currentTime = getCurrentTime();
    // Keep track of the start time so we can measure how long the main thread
    // has been blocked.
    startTime = currentTime;
    const hasTimeRemaining = true;

    // If a scheduler task throws, exit the current browser task so the
    // error can be observed.
    //
    // Intentionally not using a try-catch, since that makes some debugging
    // techniques harder. Instead, if `scheduledHostCallback` errors, then
    // `hasMoreWork` will remain true, and we'll continue the work loop.
    let hasMoreWork = true;
    try {
      hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
    } finally {
      if (hasMoreWork) {
        // If there's more work, schedule the next message event at the end
        // of the preceding one.
        schedulePerformWorkUntilDeadline();
      } else {
        isMessageLoopRunning = false;
        scheduledHostCallback = null;
      }
    }
  } else {
    isMessageLoopRunning = false;
  }
  // Yielding to the browser will give it a chance to paint, so we can
  // reset this.
  needsPaint = false;
};

在该方法中获得当前时间,并设置hasTimeRemain为True并执行 scheduledHostCallback 也就是flushWork方法,根据该方法确定是否有更多任务,也就是提到的hasMoreWork,如果有,继续通过schedulePerformWorkUntilDeadline方法调度,如果没有就退出。而在flushWork中会执行workLoop方法,workLoop方法调用时会执行任务。

function workLoop(hasTimeRemaining, initialTime) {
  let currentTime = initialTime;
  advanceTimers(currentTime);
  currentTask = peek(taskQueue);
  while (
    currentTask !== null &&
    !(enableSchedulerDebugging && isSchedulerPaused)
  ) {
    if (
      currentTask.expirationTime > currentTime &&
      (!hasTimeRemaining || shouldYieldToHost())
    ) {
      // This currentTask hasn't expired, and we've reached the deadline.
      break;
    }
    const callback = currentTask.callback;
    if (typeof callback === 'function') {
      currentTask.callback = null;
      currentPriorityLevel = currentTask.priorityLevel;
      const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
      if (enableProfiling) {
        markTaskRun(currentTask, currentTime);
      }
      const continuationCallback = callback(didUserCallbackTimeout);
      currentTime = getCurrentTime();
      if (typeof continuationCallback === 'function') {
        currentTask.callback = continuationCallback;
        if (enableProfiling) {
          markTaskYield(currentTask, currentTime);
        }
      } else {
        if (enableProfiling) {
          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 {
    const firstTimer = peek(timerQueue);
    if (firstTimer !== null) {
      requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
    }
    return false;
    
  }
}

在workLoop方法中会得到taskQueue中最需要执行的任务然后执行,由于是分时间片执行,在执行过程中会在每个时间片执行一次任务函数,并且反复检测任务函数的返回值,假如返回值为函数,说明尚未完成,那么循环执行,假如返回值为null,说明该任务函数已完成,那就在taskQueue中寻找下一个任务继续执行。而workLoop循环退出有两种可能,没有剩余时间分给时间片或者没有下一个任务,假如此时hasTimeRemaining为false,说明没有剩余时间,就需要退出,但保留当前任务,那么此时workLoop的返回值为true,代表hasmorework,那就继续通过messageChannel进行调度。假如currentTask为null,说明taskQueue中任务已完成,已经没有下一个任务,那就就执行requestHostTimeout方法等待从timerQueue中获取任务放入taskQueue中并且返回false,代表没有更多工作,调度已经完成。这是scheduler中比较重要的部分。

JS中的偏函数

这里说一下JS中的偏函数,由bind实现,可以进行预定义参数,因为在workLoop中只会传入didUserCallbackTimeout这一布尔值,所以需要预先绑定root。

    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );

另外,在ensureRootIsScheduled方法中可以检查任务,如果出现新的较高优先级的任务,就会使用执行cancelCallback,将当前任务的callback设置为null,从而使workLoop将正在执行的任务送出taskQueue,造成中断,实现优先级调度。     

总结

介绍了react-reconciler与scheduler的关系,分析了react中如何通过scheduler实现对多任务的优先级调度以及在scheduler的workLoop中单个任务是否完成的判断,还补充解释了JS中关于偏函数的知识。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值