Jinuss's blog Jinuss's blog
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

Web、WebGIS技术博客
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 《React源码》笔记
  • Scheduler
东流
2026-01-28
目录

Scheduler的任务优先级

# 概览

React应用中的更新(如useEffect、setState等)都会被转化为一个任务task,并将其分配给Scheduler进行调度。每个任务都有一个优先级priority,而Scheduler会根据优先级来决定任务的执行顺序。

# 优先级

# 优先级定义

// React 中的优先级定义(数值越小,优先级越高)
const NoPriority = 0;           // 无优先级
const ImmediatePriority = 1;    // 立即执行优先级
const UserBlockingPriority = 2; // 用户阻塞优先级
const NormalPriority = 3;      // 正常优先级
const LowPriority = 4;         // 低优先级
const IdlePriority = 5;        // 空闲优先级
1
2
3
4
5
6
7

# 优先级具体含义

  1. ImmediatePriority
  • 用途:需要立即同步执行的任务
  • 场景:
    • 离散的用户输入(如点击、按键)
    • 同步的React渲染
    • 在并发模式Concurrent Mode中,某些需要立即执行的副作用
  • 特点:不能被中断,必须立即完成
  1. UserBlockingPriority
  • 用途:用户交互相关的任务
  • 场景:
    • 连续的用户输入(如拖拽、滚动)
    • 动画更新
    • 用户触发的状态更新
  • 特点:需要在下一帧前完成,确保流畅的用户体验
  1. NormalPriority
  • 用途:默认的任务优先级
  • 场景:
    • 大部分状态更新
    • 网络请求完成后的渲染
    • 普通的副作用
  • 特点:可以被更高优先级的任务打断
  1. LowPriority
  • 用途:可以延迟执行的任务
  • 场景:
    • 数据预加载
    • 非关键的渲染
    • 分析日志
  • 特点:在浏览器空闲时执行
  1. IdlePriority
  • 用途:在浏览器完全空闲时执行的任务
  • 场景:
    • 在非必要的后台任务
    • 性能监控
    • 离线数据同步
  • 特点:只在浏览器空闲时执行,可能永远不会执行。

# 优先级作用

Scheduler调度器通过两个队列来管理调度任务:taskQueue任务队列和timerQueue定时器队列,以下是它们的区别:

  1. taskQueue任务队列
  • 存储可立即执行的任务
  • 按expirationTime排序
  • 存储当前需要执行的任务
  1. timerQueue定时器队列
  • 存储延迟执行的任务
  • 按startTime排序
  • 用于管理未来要执行的任务

这两个队列都是按照最小堆排序,而优先级就是排序的关键,优先级最小的在队列的最前面。

# 任务的注册unstable_scheduleCallback

unstable_scheduleCallback是Scheduler调度器提供给Reconciler协调器的方法,用于创建任务,并将其放入队列中进行调度执行。其源码实现如下:

function unstable_scheduleCallback(priorityLevel, callback, options) {
  // 获取当前时间
  var currentTime = getCurrentTime();
  var startTime;
  // 判断参数options是否有值,其delay值为延迟执行的时间,若设置了delay,则startTime为currentTime和delay之和;反之,startTime就是currentTime,表示需要立即执行
  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 = -1;
      break;
    case UserBlockingPriority:
      timeout = 250;
      break;
    case IdlePriority:
      timeout = Math.pow(2, 30) - 1;
      break;
    case LowPriority:
      timeout = 10000;
      break;
    case NormalPriority:
    default:
      timeout = 5000;
      break;
  }

  // 计算过期时间
  var expirationTime = startTime + timeout;

  // 创建任务对象
  var newTask = {
    id: taskIdCounter++, // 唯一标识
    callback, // 实际要执行的任务
    priorityLevel, // 原始优先级
    startTime, // 何时可以开始执行
    expirationTime, // 何时必须执行(过期必须执行)
    sortIndex: -1, // 在队列中的排序依据,初始值为-1
  };
 
  // 延时任务
  if (startTime > currentTime) {
    // 延时任务按照开始时间排序
    newTask.sortIndex = startTime;
    // 将任务插入到timerQueue队列中,push方法会使得timerQueue队列重排序,sortIndex最小的任务优先级最高,会在队列最前面
    push(timerQueue, newTask);
    // 若任务队列为空,且新任务是定时器队列最早的任务,则满足条件
    if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
      // 若 设置过定时器,则取消定时器
      if (isHostTimeoutScheduled) {
        cancelHostTimeout();
      } else {
        // 修改定时器标志
        isHostTimeoutScheduled = true;
      }
      // 调用requestTimeout方法,创建定时器
      requestHostTimeout(handleTimeout, startTime - currentTime);
    }
  } else {
    // 若没有设置延迟时间delay,则将任务的过期时间设为任务的sortIndex
    newTask.sortIndex = expirationTime;
    // 将新任务放到taskQueue任务队列中
    push(taskQueue, newTask);
    // 若没有正在进行的调度且没有正在执行的工作,则调用requestHostCallback
    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback();
    }
  }
  // 返回任务对象
  return newTask;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

# 延时任务队列

# requestHostTimeout

requestHostTimeout方法会创建一个定时器,在ms后,调用callback方法。在注册延时任务时,满足条件后,会在delay毫秒后调用的callback回调就是handleTimeout方法。

function requestHostTimeout(callback, ms) {
  taskTimeoutID = localSetTimeout(() => {
    callback(getCurrentTime());
  }, ms);
}

1
2
3
4
5
6

# handleTimeout

handleTimeout方法在延迟时间到了后就会触发。

function handleTimeout(currentTime) {
  // 将定时器标志置为false,方便后续创建定时器
  isHostTimeoutScheduled = false;

  // 处理定时器队列
  advanceTimers(currentTime);

  // 若此时没有任务执行
  if (!isHostCallbackScheduled) {
    // 任务队列不为空
    if (peek(taskQueue) !== null) {
      // 修改任务执行标志
      isHostCallbackScheduled = true;
      // 调用requestHostCallback调度任务
      requestHostCallback();
    } else {
    // 若任务队列为空,则从定时器队列中取出优先级最高的定时器任务  
      const firstTimer = peek(timerQueue);
      // 若定时器任务存在,则调用requestHostTimeout创建新的定时器 
      if (firstTimer !== null) {
        requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# advanceTimers

advanceTimers方法用于处理定时器队列和任务队列,具体来讲就是遍历定时器队列,将定时器队列中过期的任务按照优先级放在任务队列中。

function advanceTimers(currentTime) {
  // 取出定时器队列中优先级最高的任务
  let timer = peek(timerQueue);
  while (timer != null) {
    // 若定时器任务的回调为null,则调用pop方法,移除该任务
    if (timer.callback === null) {
      pop(timerQueue);
    } else if (timer.startTime <= currentTime) {
      // 若该任务的开始时间小于或等于当前时间,则说明该任务已过期,也需要移除该任务
      pop(timerQueue);
      // 修改任务的sortIndex为expirationTime过期时间
      timer.sortIndex = timer.expirationTime;
      // 将该任务放到任务队列中
      push(taskQueue, timer);
    } else {
      // 其他情况,跳出循环
      return;
    }
    // 从定时器队列中,取下一个优先级次高的任务
    timer = peek(timerQueue);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 任务队列

从上面的分析可以看出,延时任务队列的任务最终也会放入到任务队列中去进行调度执行。任务队列的调度会调用requestHostCallback方法。

# requestHostCallback

function requestHostCallback() {
  if (!isMessageLoopRunning) {
    isMessageLoopRunning = true;
    schedulePerformWorkUntilDeadline();
  }
}
1
2
3
4
5
6

requestHostCallback的内部就是根据标志isMessageLoopRunning调用schedulePerformWorkUntilDeadline方法,而该方法设计的很巧妙。

# schedulePerformWorkUntilDeadline

const localSetTimeout = typeof setTimeout === 'function' ? setTimeout:null;

const localSetTimeout = typeof setImmediate !=='undefined'? setImmediate:null; 

let schedulePerformWorkUntilDeadline;
if(typeof localSetImmediate === 'function'){
  schedulePerformWorkUntilDeadline = ()=>{
    localSetImmediate(performWorkUntilDeadline);
  }
}else if(typeof MessageChannel !== 'undefined'){
   const channel = new MessageChannel();
   const port = channel.port2;
   channel.port1.onmessage = performWorkUntilDeadline;  
   schedulePerformWorkUnitDeadline=()=>{
    port.postMessage(null)
   } 
}else{
  schedulePerformWorkUntilDeadline = ()=>{
    localSetTimeout(performWorkUntilDeadline,0)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Scheduler调度优先使用setImmediate,因为它不会阻止Node.js进程退出,其次考虑使用MessageChannel消息通道,它的执行时机是在当前任务完成后、渲染之前立即执行回调,且没有4ms的最小延迟,兜底方案是setTimeout定时器,该定时器在前者之后才执行,而且会有4ms的限制。

通过上述逐渐降级的兼容方案,实现了任务的异步调度。

编辑 (opens new window)
上次更新: 2026/02/05, 09:03:30
最近更新
01
Scheduler任务调度执行
02-02
02
Scheduler中的最小二叉堆
01-26
03
performWorkOnRoot方法解析
01-24
更多文章>
Theme by Vdoing | Copyright © 2024-2026 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式