《深入浅出react》总结之 10.7 scheduler 异步调度原理

进入调度 scheduleCallback

首先上一下 流程图,

我们还是将同步任务和异步任务分开讲解

一、同步更新的流程(isSync 分支)

同步更新是指‌立即执行 ‌的更新任务(如用户交互事件、强制同步场景),核心逻辑是:任务直接进入任务队列,由 workLoop 立即执行。

流程步骤:

  1. 判断同步更新 ‌:当任务到达 一次新的更新任务 时,判断是否为同步更新(如 isSync === true)。
  2. 创建任务 ‌:同步更新需先通过 schedule_callback 创建一个 task(任务对象)。
  3. 进入任务队列 ‌:将 task 直接放入 taskQueue(或 timeQueue,后续由 advanceTimers 合并到 taskQueue)。
  4. 触发帧执行 ‌:通过 requestHostCallback 请求浏览器"下一帧",让 workLoop 调度执行任务。
  5. 执行更新 ‌:workLooptaskQueue 中取出任务并执行,完成同步更新。

我们先解释一下 几个名词:

taskQueue: 里面存的都是过期的任务,依据任务的过期时间 (expirationTime)排序,需要在调度的 workLoop 中循环执行完这些任务。

timerQueue: 里面存的都是没有过期的任务,依据任务的开始时间 (startTime)排序,在调度 workLoop 中会用 advanceTimers 检查任务是否过期,如果过期了,放入 taskQueue 队列。

这里要明确一件事:React 的更新任务最后都是放在 taskQueue 中的

为什么同步任务会直接把任务放进 taskQueue 呢?

优先级机制的绕过

同步任务默认标记为最高优先级(如 ImmediatePriority),其 expirationTime 设置为 -1-已经过期,强制 workLoop 优先处理,无需参与 timeQueue 的优先级排序

也就是它直接会走 超时分支,

二、非同步更新的流程(isSync === false 分支)

非同步更新是指‌延迟执行 ‌的更新任务(如批量更新、异步调度场景),核心逻辑是:任务先按优先级和超时规则排队,最终由 requestHostCallback 触发 workLoop 执行。

流程步骤:

  1. 判断非同步更新 ‌:当任务到达 一次新的更新任务 时,判断是否为非同步更新(如 isSync === false)。

  2. 设置优先级 ‌:通过 priorityLevel 为任务分配优先级(如 user-blockingnormal 等)。

  3. 创建任务 ‌:通过 schedule_callback 创建一个 task,任务携带优先级、回调等信息。

  4. 超时检测‌:检查任务是否"超时"(如优先级高但等待时间过长)。

    • 若‌超时 ‌:直接进入 超时任务处理,将 task 放入 taskQueue,后续由 workLoop 执行。
    • 若‌未超时 ‌:将 task 放入 timeQueue(按优先级和超时规则排队)。
  5. 触发延迟执行 ‌:通过 requestHostTimeout 请求延迟时间后执行 handleTimeout

  6. 处理延迟任务 ‌:handleTimeout 调用 advanceTimers,将 timeQueue 中到期的任务移入 taskQueue

  7. 触发帧执行 ‌:通过 requestHostCallback 请求浏览器"下一帧",让 workLoop 调度执行任务。

  8. 执行更新 ‌:workLooptaskQueue 中取出任务并执行,完成非同步更新。

再科普几个函数

requestHostTimeout

当一个任务没有超时,那么 React 把它放入 timerQueue 中了,但是它什么时候执行呢?

这个时候Schedule 用 requestHostTimeout 让 一 个 未 过 期 的 任 务 能 够 到 达 恰 好 过 期 的 状 态,那 么 需 要 延 迟startTime-currentTime 毫秒就可以了。requestHostTimeout 就是通过 setTimeout 来进行延时指定时间的。

js 复制代码
requestHostTimeout = function (cb, ms) {

    _timeoutID = setTimeout(cb, ms);};

    cancelHostTimeout = function () {clearTimeout(_timeoutID);
};

requestHostTimeout 延时执行 handleTimeout,cancelHostTimeout 用于清除当前的延时器。 也就是穿的cb就是 handleTimeout

handleTimeout

延时指定时间后,调用的 handleTimeout 函数会把任务重新放在 requestHostCallback 调度。

js 复制代码
function handleTimeout(){
    isHostTimeoutScheduled = false;
    /* 将 timeQueue 中过期的任务,放在 taskQueue 中。*/
    advanceTimers(currentTime);
    /* 如果没有处于调度中 */
    if(!isHostCallbackScheduled){
         /* 判断有没有过期的任务,*/
         if (peek(taskQueue) ! = = null) {
             isHostCallbackScheduled = true;
             /* 开启调度任务 */
             requestHostCallback(flushWork);
         }
    }
}
requestHostCallback
js 复制代码
let scheduledHostCallback = null
/* 建立一个消息通道 */
var channel = new MessageChannel();
/* 建立一个 port 发送消息 */
var port = channel.port2;
channel.port1.onmessage = function(){
     /* 执行任务 */
     scheduledHostCallback()
     /* 执行完毕,清空任务 */
     scheduledHostCallback = null
};
/* 向浏览器请求执行更新任务 */
requestHostCallback = function (callback) {
     scheduledHostCallback = callback;
     if (!isMessageLoopRunning) {
         isMessageLoopRunning = true;
         port.postMessage(null);
     }
};

在一次更新中,Scheduler 会调用 requestHostCallback,把更新任务赋值给 scheduledHostCallback,然后 port2 向 port1 发起 postMessage 消息通知。

port1 会通过 onmessage,接受来自 port2 的消息,然后执行更新任务 scheduledHostCallback

在 Scheduler 中就是通过如上 MessageChannel 的方式向浏览器请求是否有空闲时间执行下一个更新任务(scheduledHostCallback)

所以那么调度 requestHostCallback。本质上调度的是 flushWork(依次更新过期任务队列中的任务)。

Concurrent 模式如何中断渲染

在React的异步渲染流程中,当Reconciler进入异步workLoop后,会通过shouldYield来判断是否需要中断当前任务。

shouldYield的核心原理是基于时间切片机制,它会检查当前任务的执行时间是否超过了规定的时间片(通常约为5ms)。

若执行时间未超过时间片,且没有更高优先级的任务插入,shouldYield返回false,当前任务继续执行;

若执行时间超出时间片,或者有更高优先级的任务(如用户交互等)需要处理,shouldYield返回true,此时当前任务会被中断, Reconciler会将控制权交还给浏览器,让浏览器进行事件处理、Layout、Paint等操作。

待浏览器空闲后,Scheduler会重新调度被中断的任务,恢复workLoop继续执行剩余的任务单元。这种机制实现了任务的可中断和恢复,避免了长时间占用主线程导致的页面卡顿,保证了应用的流畅性。

好了,scheduler 异步调度原理 我们就这样讲完了,如果你能把这张图盲画出来就完美了,后面我们要讲讲hook了,下篇文章见!

相关推荐
花菜会噎住12 分钟前
Vue3核心语法进阶(computed与监听)
前端·javascript·vue.js
花菜会噎住36 分钟前
Vue3核心语法基础
前端·javascript·vue.js·前端框架
全宝36 分钟前
echarts5实现地图过渡动画
前端·javascript·echarts
vjmap36 分钟前
MCP协议:CAD地图应用的AI智能化解决方案(唯杰地图MCP)
前端·人工智能·gis
simple_lau1 小时前
鸿蒙设备如何与低功耗蓝牙设备通讯
前端
啃火龙果的兔子2 小时前
解决 Node.js 托管 React 静态资源的跨域问题
前端·react.js·前端框架
ttyyttemo2 小时前
Compose生命周期---Lifecycle of composables
前端
以身入局2 小时前
FragmentManager 之 addToBackStack 作用
前端·面试
练习前端两年半2 小时前
Vue3 源码深度剖析:有状态组件的渲染机制与生命周期实现
前端·vue.js