React Scheduler 原理

React Scheduler 原理

学习react也有一段时间了,最近零零碎碎看了些东西,总觉得改写点东西沉淀下,联系到react快速响应的理念,我觉得时间切片的使用是再出色不过了,时间切片的使用离不开scheduler,那就谈谈scheduler吧



前言

一、scheduler是什么?

react16开始整个架构分成了三层,scheduler,Reconciler,renderer,因为为了实现将一个同步任务变成异步的可中断的任务,react提出了fiber,因为最开始用的是stack,任务是无法中断的,js执行时间太长时会影响页面的渲染造成卡顿,fiber中任务是可以终端,但是中断的任务怎么连上,什么时间执行,哪个先执行,这都属于是新的问题,因此scheduler出生了,以浏览器是否有剩余时间作为任务中断的标准,那么我们需要一种机制,当浏览器有剩余时间时,scheduler会通知我们,同时scheduler会进行一系列的任务优先级判断,保证任务时间合理分配。总结下scheduler的两个功能:1,时间切片 2,优先级调度

二、时间切片

JS脚本执行和浏览器布局、绘制不能同时执行。在每16.6ms时间内,需要完成 JS脚本执行 ----- 样式布局 ----- 样式绘制,当JS执行时间过长,超出了16.6ms,这次刷新就没有时间执行样式布局和样式绘制了。页面掉帧,造成卡顿。时间切片是在浏览器每一帧的时间中,预留一些时间给JS线程,React利用这部分时间更新组件,预留的初始时间是5ms。超过5ms,React将中断js,等下一帧时间到来继续执行js。其实浏览器本身已经实现了时间切片的功能,这个API叫requestIdleCallback,requestIdleCallback 是 window 属性上的方法,它的作用是在浏览器一帧的剩余空闲时间内执行优先度相对较低的任务。
但是由于requestIdleCallback 的这两个缺陷,react决定自己模拟时间切片
1.浏览器兼容不好的问题
2.requestIdleCallback 的 FPS 只有 20,也就是 50ms 刷新一次,远远低于页面流畅度的要求
回顾一个知识浏览器在16.6ms里面要做哪些事情

宏任务-- 微任务 -- requestAnimationFrame -- 浏览器重排/重绘 -- requestIdleCallback

我们可以一起来看下时间切片应该放在哪里,首先排除requestIdleCallback,缺点上文已经提到了,实际上时间切片是放在宏任务里面的,可以先说下为什么不放在其他地方的原因:

1.为什么不是微任务里面
微任务将在页面更新前全部执行完,所以达不到「将主线程还给浏览器」的目的。
2.为什么不使用requestAnimationFrame
如果第一次任务调度不是由 rAF() 触发的,例如直接执行 scheduler.scheduleTask(),那么在本次页面更新前会执行一次 rAF() 回调,该回调就是第二次任务调度。所以使用 rAF() 实现会导致在本次页面更新前执行了两次任务。
为什么是两次,而不是三次、四次?因为在 rAF() 的回调中再次调用 rAF(),会将第二次 rAF() 的回调放到下一帧前执行,而不是在当前帧前执行。
另一个原因是 rAF() 的触发间隔时间不确定,如果浏览器间隔了 10ms 才更新页面,那么这 10ms 就浪费了。(现有 WEB 技术中并没有规定浏览器应该什么何时更新页面,所以通常认为是在一次宏任务完成之后,浏览器自行判断当前是否应该更新页面。如果需要更新页面,则执行 rAF() 的回调并更新页面。否则,就执行下一个宏任务。)
3.既然是宏任务,那么是settimeout吗?
递归执行 setTimeout(fn, 0) 时,最后间隔时间会变成 4 毫秒,而不是最初的 1 毫秒,因为settimeout的执行时机是和js执行有关的,递归是会不准,最终使用 MessageChannel 产生宏任务,但是由于兼容,如果当前宿主环境不支持MessageChannel,则使用setTimeout。

在React的render阶段,开启Concurrent Mode时,每次遍历前,都会通过Scheduler提供的shouldYield方法判断是否需要中断遍历,使浏览器有时间渲染:

function workLoopConcurrent() {
  // Perform work until Scheduler asks us to yield
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

是否中断的依据,最重要的一点便是每个任务的剩余时间是否用完。
在Schdeduler中,为任务分配的初始剩余时间为5ms。如果shouldYield为true,任务就会中断,中断之后再次执行就要用到调度了

三、任务调度

Scheduler对外暴露了一个方法unstable_runWithPriority,这个方法可以用来获取优先级

unction unstable_runWithPriority(priorityLevel, eventHandler) {
  switch (priorityLevel) {
    case ImmediatePriority:
    case UserBlockingPriority:
    case NormalPriority:
    case LowPriority:
    case IdlePriority:
      break;
    default:
      priorityLevel = NormalPriority;
  }

//。。。省略
}

可以看到有5种优先级,比如,我们知道commit阶段是同步执行的。可以看到,commit阶段的起点commitRoot方法的优先级为ImmediateSchedulerPriority。
ImmediateSchedulerPriority即ImmediatePriority的别名,为最高优先级,会立即执行。可是优先级只是一个名称,react如何判断优先级的高低呢,这里我觉得和操作系统里面的一些概念还是挺相似的
给不同任务给上过期时间,谁快过期了就先执行谁

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;
// Times out immediately
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
// Eventually times out
var USER_BLOCKING_PRIORITY_TIMEOUT = 250;
var NORMAL_PRIORITY_TIMEOUT = 5000;
var LOW_PRIORITY_TIMEOUT = 10000;
// Never times out
var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;

可以看到 IMMEDIATE_PRIORITY_TIMEOUT =-1,说明比当前时间还早,已经过期,必须快执行,初此之外,react新增了两个队列:已就绪任务 ,未就绪任务
所以,Scheduler存在两个队列:timerQueue:保存未就绪任务,taskQueue:保存已就绪任务
每当有新的未就绪的任务被注册,我们将其插入timerQueue并根据开始时间重新排列timerQueue中任务的顺序。当timerQueue中有任务就绪,即startTime <= currentTime,我们将其取出并加入taskQueue。
取出taskQueue中最早过期的任务并执行他。

总结

简单介绍下scheduler的原理,其实要更多了解scheduler,还要再看看lane模型,这块之后再说吧,还有fiber啥的,有时间再写。文章如果有不对的地方,欢迎指正

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值