zoukankan      html  css  js  c++  java
  • 老大react说:schedule,我们今年的小目标是一个亿

    老大react说:schedule,我们今年的小目标是一个亿

    hello,这里是潇晨,今天来讲个故事

    讲个故事:

    从前,有家z公司,z公司的ceo叫react,它收下有个小弟或者叫小leader,schedule

    schedule每天负责消化老大react画的大饼,然后将拆解成一个个小小的task,给下面的小弟去完成,并且负责划分优先级,调度小弟们的任务排序。

    schedule怎样给这些任务划分优先级呢,它想了个最简单的办法,用deadline或者过期时间,给这些task划分优先级,过期时间越短说明这个任务越紧急,赶紧分配苦力(下面的小弟)去完成,过期时间越长,说明这个task越不紧急,可以以后慢慢干,还有一类task已经过了它的deadline,这个过期的任务优先级最高,没办法,延期之后也是要完成的,可怜了程序员小哥哥了。

    image-20211129163824936

    于是小leader,scheduler把老板的饼掰碎了,然后给这些小task按照deadline排了个优先级,于是程序员小哥哥开始接任务了

    程序员小哥哥A接受了task1task2,于是他给自己排了个任务清单,按照优先级先做task1,然后做task2,于是小哥进入密集开发中(render阶段),正在做task1

    image-20211129163900829

    但是天又不测风云,老板根据业务的需要,给scheduler下达了一个非常紧急的需求,苦了程序员小哥了,scheduler说,唉,没办法呀,加加班,把这个非常紧急的需求现插个队吧,程序员小哥单线程的,每次只能做一个task,于是插个队,加加班做最紧急的需求task0吧。

    image-20211129163918924

    接下来就是密集的加班中。。。(这一阶段称为render阶段)

    终于在不屑的努力下,最终程序员小哥还是加班加点的把所有任务完成了,交给测试验证(commit阶段),

    image-20211129164136855

    以上情况是有紧急任务时候的打断,还有一种情况是老板给的大饼很难消化,但是这个task2还没到达deadline,程序员小哥在做这个任务的时候遇到了困难,所以就先放一放吧,反正是个艰巨的任务,在空闲的时候在做吧,先完成优先级高的task0task1,有时间在做task2

    image-20211129171041192

    进入正题:

    当我们在类似下面的搜索框组件进行搜索时会发现,组件分为搜索部分和搜索结果展示列表,我们期望输入框能立刻响应,搜素列表可以有等待的时间,如果搜索列表数据量很大,在进行渲染的时候,我们又输入了一些文字,因为用户输入事件的优先级是很高的,所以就要停止结果列表的渲染,这就引出了不同任务之间的优先级和调度

    react源码15.5

    Scheduler

    我们知道如果应用占用较长的js执行时间,比如超过了设备一帧的时间,那么设备的绘制就会出现不流畅的现象。

    Scheduler主要的功能是时间切片和调度优先级,react在对比节点差异的时候会占用一定的js执行时间,Scheduler内部借助MessageChannel实现了在浏览器绘制之前指定一个时间片,如果react在指定时间内没执行完差异的对比,Scheduler就会强制交出执行权给浏览器

    react源码15.3

    时间切片

    ​ 在浏览器的一帧中js的执行时间如下

    react源码15.1

    requestIdleCallback是在浏览器重绘重排之后,如果还有空闲就可以执行未完成的任务,所以为了不影响重绘重排,可以在浏览器在requestIdleCallback中执行耗性能的计算,但是由于requestIdleCallback存在兼容和触发时机不稳定的问题,scheduler中采用MessageChannel来实现requestIdleCallback,如果当前环境不支持MessageChannel就采用setTimeout

    ​ 在performUnitOfWork(render阶段的起点)之后会执行render阶段和commit阶段,如果在浏览器的一帧中,cup的计算还没完成,就会让出js执行权给浏览器,这个判断在workLoopConcurrent函数中,shouldYield就是用来判断剩余的时间有没有用尽。在源码中每个时间片时5ms,这个值会根据设备的fps调整。

    function workLoopConcurrent() {
      while (workInProgress !== null && !shouldYield()) {//如果fiber链还没遍历完,没有被暂停或者打断
        performUnitOfWork(workInProgress);//执行render阶段
      }
    }
    
    function forceFrameRate(fps) {//计算时间片
      if (fps < 0 || fps > 125) {
        console['error'](
          'forceFrameRate takes a positive int between 0 and 125, ' +
            'forcing frame rates higher than 125 fps is not supported',
        );
        return;
      }
      if (fps > 0) {
        yieldInterval = Math.floor(1000 / fps);
      } else {
        yieldInterval = 5;//时间片默认5ms
      }
    }
    

    任务的暂停

    shouldYield函数中有一段,所以可以知道,如果当前时间大于任务开始的时间+yieldInterval,就打断了任务的进行。

    //deadline = currentTime + yieldInterval,deadline是在performWorkUntilDeadline函数中计算出来的
    if (currentTime >= deadline) {
      //...
    	return true
    }
    

    调度优先级

    ​ 在Scheduler中有两个函数可以创建具有优先级的任务

    • runWithPriority:以一个优先级执行callback,如果是同步的任务,优先级就是ImmediateSchedulerPriority

      function unstable_runWithPriority(priorityLevel, eventHandler) {
        switch (priorityLevel) {//5种优先级
          case ImmediatePriority:
          case UserBlockingPriority:
          case NormalPriority:
          case LowPriority:
          case IdlePriority:
            break;
          default:
            priorityLevel = NormalPriority;
        }
      
        var previousPriorityLevel = currentPriorityLevel;//保存当前的优先级
        currentPriorityLevel = priorityLevel;//priorityLevel赋值给currentPriorityLevel
      
        try {
          return eventHandler();//回调函数
        } finally {
          currentPriorityLevel = previousPriorityLevel;//还原之前的优先级
        }
      }
      
    • scheduleCallback:以一个优先级注册callback,在适当的时机执行,因为涉及过期时间的计算,所以scheduleCallbackrunWithPriority的粒度更细。

      • scheduleCallback中优先级意味着过期时间,优先级越高priorityLevel就越小,过期时间离当前时间就越近,var expirationTime = startTime + timeout;例如IMMEDIATE_PRIORITY_TIMEOUT=-1,那var expirationTime = startTime + (-1);就小于当前时间了,所以要立即执行。

      • scheduleCallback调度的过程用到了小顶堆,所以我们可以在O(1)的复杂度找到优先级最高的task,不了解可以查阅资料,或者查阅我的leetcode算法精讲系列,在源码中小顶堆存放着任务,每次peek都能取到离过期时间最近的task

      • scheduleCallback中,未过期任务task存放在timerQueue中,过期任务存放在taskQueue中。

        ​ 新建newTask任务之后,判断newTask是否过期,没过期就加入timerQueue中,如果此时taskQueue中还没有过期任务,timerQueue中离过期时间最近的task正好是newTask,则设置个定时器,到了过期时间就加入taskQueue中。

        ​ 当timerQueue中有任务,就取出最早过期的任务执行。

    function unstable_scheduleCallback(priorityLevel, callback, options) {
      var currentTime = getCurrentTime();
    
      var startTime;//开始时间
      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越小
          timeout = IMMEDIATE_PRIORITY_TIMEOUT;//-1
          break;
        case UserBlockingPriority:
          timeout = USER_BLOCKING_PRIORITY_TIMEOUT;//250
          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;//优先级越高 过期时间越小
    
      var newTask = {//新建task
        id: taskIdCounter++,
        callback//回调函数
        priorityLevel,
        startTime,//开始时间
        expirationTime,//过期时间
        sortIndex: -1,
      };
      if (enableProfiling) {
        newTask.isQueued = false;
      }
    
      if (startTime > currentTime) {//没有过期
        newTask.sortIndex = startTime;
        push(timerQueue, newTask);//加入timerQueue
        //taskQueue中还没有过期任务,timerQueue中离过期时间最近的task正好是newTask
        if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
          if (isHostTimeoutScheduled) {
            cancelHostTimeout();
          } else {
            isHostTimeoutScheduled = true;
          }
          //定时器,到了过期时间就加入taskQueue中
          requestHostTimeout(handleTimeout, startTime - currentTime);
        }
      } else {
        newTask.sortIndex = expirationTime;
        push(taskQueue, newTask);//加入taskQueue
        if (enableProfiling) {
          markTaskStart(newTask, currentTime);
          newTask.isQueued = true;
        }
        if (!isHostCallbackScheduled && !isPerformingWork) {
          isHostCallbackScheduled = true;
          requestHostCallback(flushWork);//执行过期的任务
        }
      }
    
      return newTask;
    }
    

    react源码15.2

    视频讲解(高效学习):点击学习

    往期react源码解析文章:

    1.开篇介绍和面试题

    2.react的设计理念

    3.react源码架构

    4.源码目录结构和调试

    5.jsx&核心api

    6.legacy和concurrent模式入口函数

    7.Fiber架构

    8.render阶段

    9.diff算法

    10.commit阶段

    11.生命周期

    12.状态更新流程

    13.hooks源码

    14.手写hooks

    15.scheduler&Lane

    16.concurrent模式

    17.context

    18事件系统

    19.手写迷你版react

    20.总结&第一章的面试题解答

  • 相关阅读:
    js监听全屏的事件
    java后端发送请求
    java参数转换为javaBean对象
    Cesiumjs初学第一天
    echarts设置toolTip大小和样式问题
    楼梯式导航
    SpringMybatisMapper
    ASP.NET Session丢失的情况
    C# 生成随机数
    c#实现每隔规定时间自动执行程序代码
  • 原文地址:https://www.cnblogs.com/xiaochen1024/p/15665413.html
Copyright © 2011-2022 走看看