深入React19任务调度器Scheduler
Scheduler 最小堆 MessageChannel 时间片检查它的目标只有一个推进任务但永远别饿死浏览器。调度任务React 19 最新 Scheduler 源码**packages/scheduler** 中普通任务非 Immediate同步优先级之外的任务的完整调度链unstable_scheduleCallback ↓ requestHostCallback ↓ schedulePerformWorkUntilDeadline ↓ performWorkUntilDeadline ↓ flushWork ↓ workLoop ↓ shouldYieldToHost ↓ advanceTimersScheduler 本质是一个“带时间片控制的最小堆 宏任务驱动循环”调度器。一、调度的起点unstable_scheduleCallback源码位置React 19/packages/scheduler/src/forks/Scheduler.jslet getCurrentTime () performance.now(); // 为所有任务维护了连个最小堆每次从队列里面取出来的都是优先级最高时间即将过期 // timerQueue // 延时任务队列task.startTime now —— 按 startTime 排序延迟最小的先看 // taskQueue // 立即可执行的任务task.startTime now—— 按 expirationTime 排序最紧急的先执行 // Timeout 对应的值 var IMMEDIATE_PRIORITY_TIMEOUT -1; var USER_BLOCKING_PRIORITY_TIMEOUT 250; var NORMAL_PRIORITY_TIMEOUT 5000; var LOW_PRIORITY_TIMEOUT 10000; var IDLE_PRIORITY_TIMEOUT maxSigned31BitInt; // 1073741823 /** ​ * 调度任务的入口 ​ * ​param​ {*} priorityLevel 优先级等级 ​ * ​param​ {*} callback 任务回调函数 ​ * ​param​ {*} options { delay: number } 该对象有 delay 属性表示要延迟的时间决定 expirationTime ​ * ​returns ​ */ function unstable_scheduleCallback(priorityLevel, callback, options) { // 获取当前的时间 var currentTime getCurrentTime(); var startTime; // 设置起始时间 startTime如果有延时 delay起始时间需要添加上这个延时否则起始时间就是当前时间 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; // 根据传入的优先级等级来设置不同的 timeout switch (priorityLevel) { case ImmediatePriority: timeout IMMEDIATE_PRIORITY_TIMEOUT; // -1 break; case UserBlockingPriority: timeout USER_BLOCKING_PRIORITY_TIMEOUT; // 250 break; case IdlePriority: timeout IDLE_PRIORITY_TIMEOUT; // 1073741823 break; case LowPriority: timeout LOW_PRIORITY_TIMEOUT; // 10000 break; case NormalPriority: default: timeout NORMAL_PRIORITY_TIMEOUT; // 5000 break; } // 接下来就计算出过期时间 // 只有 ImmediatePriority 任务 比当前时间要早其他任务都会不同程度的延迟 var expirationTime startTime timeout; // 创建一个新的任务 var newTask { id: taskIdCounter, // 任务 id callback, // 执行任务回调函数 export type Callback boolean ?Callback; priorityLevel, // 任务的优先级 startTime, // 任务开始时间 expirationTime, // 任务的过期时间 sortIndex: -1, // 用于小顶堆优先级排序始终从任务队列中拿出最优先的任务 }; if (enableProfiling) { newTask.isQueued false; } if (startTime currentTime) { // 说明这是一个延时任务 newTask.sortIndex startTime; // 将该任务推入到 timerQueue 的任务队列中 push(timerQueue, newTask); if (peek(taskQueue) null newTask peek(timerQueue)) { // 说明 taskQueue 里面的任务已经全部执行完毕然后从 timerQueue 里面取出一个优先级最高的任务作为此时的 newTask if (isHostTimeoutScheduled) { cancelHostTimeout(); } else { isHostTimeoutScheduled true; } // 如果是延时任务调用 requestHostTimeout 进行延时任务的调度 requestHostTimeout(handleTimeout, startTime - currentTime); } } else { // 说明不是延时任务 newTask.sortIndex expirationTime; // 设置了 sortIndex 后在任务队列里面进行一个排序 push(taskQueue, newTask); if (enableProfiling) { markTaskStart(newTask, currentTime); newTask.isQueued true; } // 最终调用 requestHostCallback 进行普通任务的调度 if (!isHostCallbackScheduled !isPerformingWork) { isHostCallbackScheduled true; requestHostCallback(); } } // 向外部返回任务 return newTask; }它的职责是根据优先级计算 timeout构造一个 Task 对象放入任务到对应的最小堆请求宿主开始调度requestHostCallback 普通任务 / requestHostTimeout 延时任务任务执行时刻IMMEDIATE_PRIORITY_TIMEOUT - currentTime - USER_BLOCKING_PRIORITY_TIMEOUT - IDLE_PRIORITY_TIMEOUT二、普通任务调用 requestHostCallback当普通任务进入taskQueue后调用requestHostCallback然后调用schedulePerformWorkUntilDeadline/** ​ * ​ * ​param​ ​{*}​ callback 是在调用的时候传入的 flushWork ​ * requestHostCallback 这个函数没有做什么事情主要就是调用 schedulePerformWorkUntilDeadline ​ */ function requestHostCallback() { if (!isMessageLoopRunning) { isMessageLoopRunning true; schedulePerformWorkUntilDeadline();// 实例化 MessageChannel 进行后面的调度 } } let schedulePerformWorkUntilDeadline; // undefined if (typeof localSetImmediate function) { // Node.js and old IE. schedulePerformWorkUntilDeadline () { localSetImmediate(performWorkUntilDeadline); }; } else if (typeof MessageChannel ! undefined) { // 大多数情况下使用的是 MessageChannel const channel new MessageChannel(); const port channel.port2; channel.port1.onmessage performWorkUntilDeadline; schedulePerformWorkUntilDeadline () { port.postMessage(null); }; } else { // setTimeout 进行兜底 schedulePerformWorkUntilDeadline () { localSetTimeout(performWorkUntilDeadline, 0); }; }schedulePerformWorkUntilDeadline根据不同的环境选择不同的生成宏任务的方式。大多数都是 MessageChannel 每一次调度推进都是一个新的宏任务浏览器中间有机会 paint三、延时任务调用 requestHostTimeout handleTimeoutReact Scheduler 不直接使用 setTimeout而是抽象成 HostConfig可在不同环境实现。requestHostTimeout(callback, ms)这个函数会安排一个底层超时回调。在浏览器环境下它一般等价于const timeoutID setTimeout(callback, ms);注意这是 ​严格意义上的延时调度​用于把 timerQueue 的任务唤醒进 taskQueue。requestHostTimeout 实际上就是调用 setTimoutout然后在 setTimeout 中调用传入的 handleTimeout。注意延时任务只需要“不会提前执行”而不需要“精准执行”所以这里使用了 setTimeoutsetTimeout 只是负责“唤醒” Scheduler不负责精度和优先级expirationTime 控制控制鉴于负责延时和必须是宏任务的特性这里使用 setTimeout 最合适。React 的调度精度来自MessageChannel 时间片检查 expirationTime 。handleTimeout 是真正被 setTimeout 调用的函数function handleTimeout(currentTime) { // 遍历 timerQueue将时间已经到了的延时任务放入到 taskQueue advanceTimers(currentTime); if (!isHostCallbackScheduled) { if (taskQueue.length 0) { // 采用调度普通任务的方式进行调度 requestHostCallback(flushWork); } else { // 如果任务仍然是延时继续设置 HostTimeout const firstTimer peek(timerQueue); if (firstTimer ! null) { const nextDelay firstTimer.startTime - currentTime; requestHostTimeout(handleTimeout, nextDelay); } } } }四、performWorkUntilDeadline这是 Scheduler 的“驱动心跳”。let startTime -1; const performWorkUntilDeadline () { if (enableRequestPaint) { needsPaint false; } if (isMessageLoopRunning) { const currentTime getCurrentTime(); // 这里的 startTime 并非 unstable_scheduleCallback 方法里面的 startTime // 而是一个全局变量默认值为 -1 // 用来测量任务的执行时间从而能够知道主线程被阻塞了多久 startTime currentTime; let hasMoreWork true; try { // flushWork 为任务中转本质上内部继续调用 workLoop 判断任务执行情况 hasMoreWork flushWork(currentTime); } finally { if (hasMoreWork) { // 上面刚刚讲过的方法根据不同的环境选择不同的生成宏任务的方式MessageChannel schedulePerformWorkUntilDeadline(); } else { isMessageLoopRunning false; } } } };它做三件事设置本次调度的时间起点调用 flushWork如果还有任务 → 再发一个 MessageChannel五、flushWork 和 workLoop 执行任务循环// flushWork 是个中转它真正执行的是 workLoop function flushWork(initialTime) { // ... // 各种开关、报错捕获... // 其实核心就是 workLoop return workLoop(initialTime); } // 不断从任务队列中取出任务执行 function workLoop(initialTime: number) { // initialTime 开始执行任务的时间 let currentTime initialTime; // advanceTimers 是用来遍历 timerQueue判断是否有已经到期的任务 // 如果有将这个任务放入到 taskQueue advanceTimers(currentTime); currentTask peek(taskQueue); while (currentTask ! null) { if (!enableAlwaysYieldScheduler) { if (currentTask.expirationTime currentTime shouldYieldToHost()) { // 任务没有过期并且需要中断任务归还主线程 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); } advanceTimers(currentTime); return true; } else { if (enableProfiling) { markTaskCompleted(currentTask, currentTime); currentTask.isQueued false; } if (currentTask peek(taskQueue)) { pop(taskQueue); } advanceTimers(currentTime); } } else { pop(taskQueue); } // 执行完再从 taskQueue 中取出一个任务 currentTask peek(taskQueue); if (enableAlwaysYieldScheduler) { if (currentTask null || currentTask.expirationTime currentTime) { break; } } } // 如果任务不为空那么还有更多的任务hasMoreTask 为 true if (currentTask ! null) { return true; } else { // taskQueue 这个队列空了那么我们就从 timerQueue 里面去看延时任务 const firstTimer peek(timerQueue); if (firstTimer ! null) { requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime); } // 没有进入上面的 if说明 timerQueue 里面的任务也完了返回 false外部的 hasMoreWork 拿到的就也是 false return false; } }任务是否可以被打断shouldYieldToHost()如果返回 true当前宏任务结束重新发 MessageChannel浏览器可以渲染六、shouldYieldToHost —— 时间片控制核心源码核心function shouldYieldToHost() { const timeElapsed getCurrentTime() - startTime; if (timeElapsed frameInterval) { return false; } return true; }其中frameInterval 5ms注意React 不等 16ms 一整帧而是每 5ms 检查一次为什么预留给浏览器 layout paint预留给输入事件七、advanceTimers —— 延迟任务晋升机制每次 workLoop 结束后会调用advanceTimers(currentTime);。它会检查 timerQueue把到时间的任务移动到 taskQueuefunction advanceTimers(currentTime) { // 取出 startTime currentTime 的 timer let timer peek(timerQueue); while (timer ! null timer.startTime currentTime) { pop(timerQueue); timer.sortIndex timer.expirationTime; push(taskQueue, timer); timer peek(timerQueue); } }这保证延迟任务不会饿死八、Scheduler 与 Fiber 的关系Scheduler 不知道 Fiber。它只负责“什么时候调用 callback”而 callback 通常是performConcurrentWorkOnRoot那里面才是beginWorkcompleteWorkcommitRoot九、任务拆分的思路来源假设有这样一段源码unstable_scheduleCallback(NormalPriority, () { heavyWork(); // 10ms });如果heavyWork()是同步执行 10msScheduler​无法中断它​因为 JS 是单线程的函数执行过程中无法被抢占。来看源码核心逻辑const continuation callback(); if (typeof continuation function) { currentTask.callback continuation; } else { pop(taskQueue); }如果 callback 返回一个函数Scheduler 会把它当成“任务的下一段”。举一个最小可理解示例// 假设这是一个“大”任务 function createBigTask(total) { let i 0; function work() { while (i total) { console.log(i); if (shouldStopManually()) { return work; // 返回自身表示还有后续 } } return null; // 完成 } return work; }调度unstable_scheduleCallback( NormalPriority, createBigTask(1000) );执行workLoop() ↓ 执行 work() ↓ 执行到一部分 ↓ 返回 work continuation ↓ Scheduler 保存 callback work ↓ shouldYieldToHost() 为 true ↓ break ↓ 下一帧继续执行在 React 里被拆分的任务不是“普通函数”而是performConcurrentWorkOnRoot。它内部会调用workLoopConcurrent() 。核心逻辑简化版while ( workInProgress ! null !shouldYield() ) { performUnitOfWork(workInProgress); }performUnitOfWork 只做一个 Fiber 节点function performUnitOfWork(unit) { const next beginWork(unit); if (next null) { completeUnitOfWork(unit); } return next; }也就是说React 把整棵 Fiber 树拆成一个个“节点单位”/“任务切片”。更形象的可以表示为一个组件树App ├─ Header ├─ Content │ ├─ List │ └─ Sidebar └─ Footer会被拆成performUnitOfWork(App) performUnitOfWork(Header) performUnitOfWork(Content) performUnitOfWork(List) performUnitOfWork(Sidebar) performUnitOfWork(Footer)每个 Fiber 节点就是一个小任务。回到 Scheduler 这段判断if ( currentTask.expirationTime currentTime shouldYieldToHost() ) { break; }当时间片用完时break 。此时workLoop 停止任务还没完成currentTask.callback 仍然是 performConcurrentWorkOnRoot下一帧MessageChannel ↓ performWorkUntilDeadline ↓ flushWork ↓ workLoop ↓ 继续执行 performConcurrentWorkOnRoot延时任务并不会立刻进入 taskQueue而是先进入 timerQueue等待被“唤醒”然后再调度。十、完整调度流程下面是一个完整的流程图unstable_scheduleCallback() ↓ 是否延时? (startTime now) / \ 是 否 ↓ ↓ push(timerQueue) push(taskQueue) ↓ ↓ requestHostTimeout(requestHostCallback) ↓ ↓ 等待 Timer requestHostCallback ↓ ↓ timeout 到时 MessageChannel ↓ ↓ handleTimeout performWorkUntilDeadline ↓ ↓ advanceTimers flushWork ↓ ↓ timerQueue → taskQueue ↓ workLoop ↓ shouldYieldToHost? ↓ 是 否 notifyBrowserPaint 执行 Callback ↓ callback 返回 continuation? ↓ 是 否 更新 Task.callback pop(taskQueue) ↓ 可能再次调度示例理解示例 1普通任务正常进入队列unstable_scheduleCallback( NormalPriority, () console.log(normal) );步骤now startTime (0 delay)放入 taskQueuerequestHostCallback(flushWork)MessageChannel 触发 performWorkUntilDeadlineflushWork → workLoop执行 tasktaskQueue 空 → 调度结束示例 2延时任务未来才执行unstable_scheduleCallback( NormalPriority, () console.log(delayed), { delay: 1000 } );步骤t0 ↓ startTime now 1000 push(timerQueue) requestHostTimeout(handleTimeout, 1000)1s 后handleTimeout 被 setTimeout 调用 ↓ advanceTimers → 取出 timerQueue 里所有 startTime 1s 的任务 → 推到 taskQueue taskQueue 变非空 ↓ requestHostCallback(flushWork) ↓ MessageChannel 回调 ↓ flushWork → workLoop → task 执行示例 3延时任务到了中间又被打断unstable_scheduleCallback( NormalPriority, step1, { delay: 1000 } ); function step1() { console.log(step1); return step2; } function step2() { console.log(step2); }执行过程t0 → timerQueue 入队 t1000 → handleTimeout → advanceTimers → taskQueue ↓ MessageChannel ↓ workLoop 执行 step1 延时任务 ↓ step1 返回 continuation step2 延时任务 ↓ 此时 workLoop 检查 shouldYieldToHost 如果 time片到 → 中断 → callback (continuation) 留在 taskQueue → requestHostCallback 执行普通任务 下次继续执行 step2 延时任务设计哲学① 延时任务不能抢占浏览器渲染如果立即调度setTimeout(() {}), Promise.then(...)React 可能会阻塞页面渲染所以必须分开先 timerQueue 等待 再 taskQueue 才跑② 延时任务按照优先级即使 delay 到了expirationTime startTime timeout如果更高优先级任务进入 taskQueueReact 会先执行高优先级的。delay控制什么时候进入 taskQueue而expirationTime控制进入 taskQueue 之后的优先级排序。③ extendable 继续推进任务一个任务返回 continuation 时currentTask.callback continuation;并且还可能继续 schedule。

相关新闻

【DFS】BISHI77数水坑

【DFS】BISHI77数水坑

思路求解代码/*** 主方法,程序的入口点** param args 命令行参数* throws IOException 可能抛出IO异常*/public static void main(String[] args) throws IOException {// 使用BufferedReader读取标准输入,用于高效读取输入数据BufferedReader br new B…

2026/7/6 6:46:03 阅读更多 →
【踩坑】MacOS26上的浏览器无法显示麦克风/摄像头列表

【踩坑】MacOS26上的浏览器无法显示麦克风/摄像头列表

转载请注明出处:小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你,欢迎[点赞、收藏、关注]哦~ 问题描述: Chrome和Edge等浏览器均无法显示麦克风和摄像头列表,但摄像头有画面、麦克风有声音。权限已授权,方法参考…

2026/7/6 6:43:36 阅读更多 →
什么是动态住宅 IP 代理?动态 IP 最常用在哪些业务

什么是动态住宅 IP 代理?动态 IP 最常用在哪些业务

一、什么是动态住宅 IP ?简单而言,动态住宅 IP 是来自真实住宅网络环境的 IP 地址,兼具 “定期更换” 特性。它既保留了住宅 IP 的高可信度,又通过动态切换机制,模拟自然用户的上网行为。 相比数据中心 IP 或静态住宅 …

2026/7/5 22:59:53 阅读更多 →

最新新闻

YOLO目标检测实战指南:从核心思想到工程部署

YOLO目标检测实战指南:从核心思想到工程部署

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度 如果你是一名计算机视觉开发者,或者正在学习AI,最近可能被一个现象级课程刷屏了:一套号称“100集”…

2026/7/6 6:47:00 阅读更多 →
STC3115芯片与PIC32MX675F512L在电池管理系统中的实战应用

STC3115芯片与PIC32MX675F512L在电池管理系统中的实战应用

1. STC3115芯片:电池监控的瑞士军刀STC3115这颗芯片在电池管理领域堪称革命性产品。作为一名长期从事嵌入式系统开发的工程师,我第一次接触这款芯片时就意识到它的独特价值——它把原本需要多个分立元件才能实现的功能,集成到了一个只有5mm5m…

2026/7/6 6:47:00 阅读更多 →
AD5593R与PIC18F4585构建可配置混合信号处理系统

AD5593R与PIC18F4585构建可配置混合信号处理系统

1. 项目概述:打造灵活可配置的ADC-DAC混合信号处理系统在嵌入式硬件开发中,模拟信号与数字信号的相互转换是连接物理世界与数字世界的桥梁。AD5593R这款来自ADI的混合信号IC,配合PIC18F4585微控制器的强大处理能力,可以构建一个高…

2026/7/6 6:44:59 阅读更多 →
Borderless Gaming终极指南:如何轻松实现游戏窗口无边框化

Borderless Gaming终极指南:如何轻松实现游戏窗口无边框化

Borderless Gaming终极指南:如何轻松实现游戏窗口无边框化 【免费下载链接】Borderless-Gaming Play your favorite games in a borderless window; no more time consuming alt-tabs. 项目地址: https://gitcode.com/gh_mirrors/bo/Borderless-Gaming 厌倦…

2026/7/6 6:44:59 阅读更多 →
3分钟搞定Unity游戏汉化:XUnity.AutoTranslator实战手册

3分钟搞定Unity游戏汉化:XUnity.AutoTranslator实战手册

3分钟搞定Unity游戏汉化:XUnity.AutoTranslator实战手册 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator 你是否曾经遇到过一款优秀的Unity游戏,却因为语言障碍而无法完全享受&…

2026/7/6 6:44:59 阅读更多 →
Si5351A与ATSAME70Q21B实现高精度时钟管理方案

Si5351A与ATSAME70Q21B实现高精度时钟管理方案

1. 项目背景与核心需求在嵌入式系统和数字电路设计中,稳定的时钟信号如同人体的脉搏,是确保系统正常运转的基础。无论是汽车电子中的ECU控制单元,还是工业自动化设备中的实时通信,都需要高精度的频率参考来同步各个模块的时序。传…

2026/7/6 6:40:58 阅读更多 →

日新闻

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2与MySQL单元测试兼容性:5个关键SQL语句差异与规避方案1. 单元测试中的数据库兼容性挑战在Java开发领域,单元测试是保证代码质量的重要环节。当应用涉及数据库操作时,测试环境的搭建往往成为开发者的痛点。H2数据库因其轻量级、内存模式和快…

2026/7/6 0:01:17 阅读更多 →
Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘 【免费下载链接】rbtray A fork of RBTray from http://sourceforge.net/p/rbtray/code/. 项目地址: https://gitcode.com/gh_mirrors/rb/rbtray 你是否厌倦了Windows任务栏上密密麻麻的图标&…

2026/7/6 0:01:17 阅读更多 →
Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C 运行时库一键安装终极指南:告别DLL缺失烦恼 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾经遇到过这样的情况:下载了…

2026/7/6 0:05:19 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/5 0:03:34 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/5 0:03:34 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/5 0:07:38 阅读更多 →

月新闻