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-02-02
目录

Scheduler任务调度执行

# 概览

Scheduler调度器的任务调度,经过MessageChannel或者定时器,会触发performWorkUntilDeadline方法。本文主要介绍任务调度的执行。

# 源码分析

# performWorkUntilDeadline

performWorkUnitDeadline函数会在每一帧的时间分片内执行工作。

function performWorkUntilDeadline() {
  // 重置是否需要绘制的标志,这个标志用于在浏览器绘制之前让出主线程
  needsPaint = false;
  // 确保调度循环正在运行,在requestHostCallback中置为true
  if (isMessageLoopRunning) {
    // 获取当前时间
    const currentTime = getCurrentTime();
    // 记录开始时间
    startTime = currentTime;
    // 默认还有更多任务需要处理
    let hasMoreWork = true;
    try {
      // 调用flushWork,得到一个布尔值,表示是否还有更多的工作
      hasMoreWork = flushWork(currentTime);
    } finally {
      // 如果还有更多任务,继续调用schedulePerformWorkUntilDeadline方法,调度任务在下一帧中执行
      if (hasMoreWork) {
        schedulePerformWorkUntilDeadline();
      } else {
        // 否则停止调度循环,重置标志位
        isMessageLoopRunning = false;
      }
    }
  }
}
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

# flushWork

flushWork用于设置和清理环境,并调用workLoop

function flushWork(initialTime) {
  // 重置调度标志变量,表示已经调度了
  isHostCallbackScheduled = false;
  // 判断之前是否设置了延迟任务的超时,若设置过,则取消定时器,以及重置标志位
  if (isHostTimeoutScheduled) {
    isHostTimeoutScheduled = false;
    cancelHostTimeout();
  }
  // 标记正在执行工作
  isPerformingWork = true;
  // 保存之前的优先级,以便恢复
  const previousPriorityLevel = currentPriorityLevel;
  try {
    return workLoop(initialTime);
  } finally {
    // 清理工作
    currentTask = null;// 清空当前任务
    currentPriorityLevel = previousPriorityLevel;//恢复之前的优先级
    isPerformingWork = false;//标记工作结束
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# workLoop

workLoop就是一个循环处理任务队列,会从任务队列中取出任务并执行,直到满足退出条件(任务执行完成或应该让出主线程)

function workLoop(initialTime) {
  let currentTime = initialTime;
  // 调用 advanceTimers方法,将定时器队列中到期的任务加入到任务队列中
  advanceTimers(currentTime);
  // 取出任务队列中的优先级最高的任务
  currentTask = peek(taskQueue);
  // 循环执行任务
  while (currentTask !== null) {
    // 如果当前任务的过期时间大于当前时间,说明还没有过期,此时就需要检查是否应该让出主线程
    // 如果当前任务已经过期,那么即使此时应该让出主线程,也需要继续执行,因为过期任务必须完成
    // 因此,只有当当前任务没有过期,且应该让出主线程时,才跳出循环
    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;
      // 执行回调,并获取返回值
      const continuationCallback = callback(didUserCallbackTimeout);
      
      // 更新当前时间 
      currentTime = getCurrentTime();
      
      // 若回调返回了一个函数,则说明任务还没有完成
      if (typeof continuationCallback === "function") {
        // 此时需要将返回的函数作为新的回调
        currentTask.callback = continuationCallback;
        // 继续检查定时器队列
        advanceTimers(currentTime);
        // 返回 true,表示还有工作,继续调度,执行下次循环
        return true;
      } else {
        // 如果回调没有返回函数,则说明任务已经完成,需要从队列中移除
        if (currentTask === peek(taskQueue)) {
          pop(taskQueue);
        }
        // 继续检查定时器队列
        advanceTimers(currentTime);
      }
    } else {
      // 如果回调不是函数,则说明任务已经完成或者被取消了,直接移除
      pop(taskQueue);
    }
    // 继续从任务队列中取出下一个任务
    currentTask = peek(taskQueue);
  }
  
  // 如果任务队列中还有任务,说明是因为让出主线程而退出的循环,返回true,表示还有工作
  if (currentTask !== null) {
    return true;
  } else {
    // 如果任务队列为空,则检查定时器队列
    const firstTimer = peek(timerQueue);
    if (firstTimer != null) {
      // 如定时器队列中还有任务,就设置一个超时,在定时器任务开始时间到达时执行
      requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
    }
    // 返回 false,表示没有更多工作
    return false;
  }
}
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

# shouldYieldToHost

shouldYieldToHost函数返回一个布尔值,表示是否应该让出主线程。

const frameInterval = 5; // 5ms
function shouldYieldToHost() {
  // 若需要渲染,则返回true,否则判断时间差,任务调度执行的时间是否小于5ms,若小于,则返回false;否则返回true
  return needsPaint
    ? true
    : getCurrentTime() - startTime < frameInterval
    ? false
    : true;
}
1
2
3
4
5
6
7
8
9

# 关键逻辑解析

# 中断机制

if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {
  break;
}
1
2
3

# 条件解释

  • currentTask.expirationTime > currentTime:任务还未到期
  • shouldYieldToHost():返回一个布尔值,表示是否需要让出主线程(时间片用完了)

# 中断场景

  1. 高优先级更新任务到来
  2. 用户交互(如点击)
  3. 浏览器需要重绘
  4. 时间片用尽(默认5ms)

# 任务执行和续传

const continuationCallback = callback(didUserCallbackTimeout);
if (typeof continuationCallback === "function") {
  currentTask.callback = continuationCallback;
  return true;
}
1
2
3
4
5

续传模式:这是Fiber架构的关键,让React可以将渲染工作分为多个小任务

// React组件的渲染可能返回续传函数
function renderComponent(/* ... */) {
  // 执行一部分工作...
  if (/* 还有工作 */) {
    return renderComponent;  // 返回自身,表示需要继续
  }
  return null;  // 返回null,表示完成
}
1
2
3
4
5
6
7
8

# 两个队列

任务队列(taskQueue)和定时器队列(timerQueue)。定时器队列中的任务还没有到开始时间,当时间到达时,advanceTimers会将它们移到任务队列中。

# 超时处理

如果当前没有立即需要执行的任务,但是定时器队列中有未来的任务,那么会通过requestHostTimeout设置一个超时,在任务开始时间到达时再执行

# 总结

本文中的函数是Scheduler调度器的核心功能,共同实现了时间分片,任务可中断/恢复,基于优先级调度,延迟任务处理和异常安全的执行。

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