EventLoop介绍且在flutter中扮演的角色

54 阅读5分钟

EventLoop

EventLoop本质上就是事件循环机制

先探讨EventLoop在前端的应用

地球人都知道Javascript语言的一大特点就是单线程,这意味这它同一时间只能干一件事,特别的专一。而单线程就意味着所有任务都得排队来,前一个任务干完了才能执行下一个任务。 后来设计者发现,假如上个任务是个响应速度很慢的Ajax请求,那下一个任务不得干等着完犊子了嘛。于是乎,所有任务被分为同步任务和异步任务。具体来说,js的执行机制如下:

  1. 所有同步任务在主线程上执行
  2. 主线程之外,还存在个任务队列,异步任务有了运行结果,就会在任务队列中放置一个事件
  3. 执行栈中所有的同步任务执行完毕,就会从任务队列中取事件进入执行栈开始执行
  4. 主线程不断重复第三步

主线程从任务队列中读取事件的过程是不断循环的,所以整个运行机制又称为Event Loop

同步任务指:函数方法中:除了异步调用之外的代码,称之为同步任务

例如:

// 同步任务
console.log('1');
// 异步任务
setTimeout(function() {
    console.log('2')
});
// 同步任务
console.log('3');

运行结果:同步任务优先执行,异步代码最后执行。结果:1,3,2

微任务与宏任务

假如js按照真正的单线程执行的话,一旦遇到任务繁多的情况,势必执行效率会下降。为了解决这个问题,就需要利用微任务和宏任务来模拟“多线程”提高执行效率。通常我们把由宿主发起的任务称为宏任务,由JavaScript引擎发起的任务称为微任务

常见宏任务:setTimeout(),setInterval(),setImmediate()

常见微任务:Promise().then(function(){}),new MutationObserver(),process.nextTick()

需要注意的是Promise()这个构造函数属于同步任务,而.then()里面的函数才会被推入到微任务队列里。

setTimeout(function () {
  console.log(1);
});

new Promise(function (resolve, reject) {
  console.log(2);
  resolve(3);
}).then(function (val) {
  console.log(val);
  console.log(4);
});

//2 3 4 1

按照JS的执行顺序是先执行主线程上代码,当同步任务执行完毕,执行栈清空后,再去微任务队列里取微任务执行,当执行栈为空后,我们再去宏任务队列中取宏任务执行。 那么我们从上往下看,首先setTimeout是个宏任务,那么我们把它推入到宏任务队列里,下面Promise的构造函数则属于同步任务,那么我们在控制台上打印出个2,resolve(3)我们会传递给then()方法里的回调,而.then()里面的函数属于微任务,则把其推入到微任务队列里,则得出2 3 4 1的结果。

总结一下事件循环闭环流程

当主线程同步任务执行完毕,执行栈为空时,执行以下步骤

  1. 执行微任务队列 a. 选择微任务队列中最早的任务(任务X)

    b. 如果微任务队列为空,则跳转到步骤g

    c. 将当前正在运行的任务设置为任务X

    d. 运行任务X

    e. 将当前正在运行的任务设置为空,删除任务X

    f. 选择微任务队列中下一个最早的任务,跳转到步骤b

    g. 完成微任务队列

  2. 选择宏任务队列中最早的任务(任务Y)

  3. 将当前正在运行的任务设置为任务Y

  4. 运行任务Y,先执行其中的同步任务

  5. 跳转到步骤1

  6. 将当前正在运行的任务设置为空,删除任务Y结束本次Loop循环

  7. 跳转到步骤2

Flutter中的宏任务与微任务

常见的宏任务:Timer、Future、Future.delayed、async/await

常见的微任务:scheduleMicrotask、Future.microtask、SchedulerBinding.instance.addPostFrameCallback、WidgetsBinding.instance.addPostFrameCallback

需要注意的是:WidgetsBinding.instance.addPostFrameCallback 与 SchedulerBinding.instance.addPostFrameCallback虽然也是微任务,但是他们两者的调用是在widget构建已完成之后,才执行.

测试一下大家弄明白宏任务与微任务没。试着推敲一下执行的顺序

SchedulerBinding.instance.addPostFrameCallback((_) {
  print("SchedulerBinding");
});

WidgetsBinding.instance.addPostFrameCallback((_) {
  print("WidgetsBinding");
});

Timer.run(() {
  print("Timer");
});

scheduleMicrotask(() {
  print("scheduleMicrotask");
});

Future<void>.microtask(() {
  print("Future Microtask");
});

Future<void>(() {
  print("Future");

  Future<void>.microtask(() {
    print("Microtask from Event");
  });
});

Future<void>.delayed(Duration.zero, () {
  print("Future.delayed");

  Future<void>.microtask(() {
    print("Microtask from Future.delayed");
  });
});

输出结果如下:

I/flutter (31989): scheduleMicrotask
I/flutter (31989): Future Microtask
I/flutter (31989): SchedulerBinding
I/flutter (31989): WidgetsBinding
I/flutter (31989): Timer
I/flutter (31989): Future
I/flutter (31989): Microtask from Event
I/flutter (31989): Future.delayed
I/flutter (31989): Microtask from Future.delayed

为什么要设计微任务

总的来说,微任务解决了宏任务执行时机不可控的问题。 假设我们只有主线程和宏任务,那么当主线程上的同步任务执行完成之后,就会去宏任务队列里去取事件放到主线程的执行栈中去执行,此时宏任务A要执行多久我们就没办法控制,假如宏任务B中有一个特别紧急的操作,要进行UI的渲染的话就没法及时操作,只能在这里干等着,听天由命。而引入微任务后就会发生改变,我们可以把这些特别紧急的操作放到微任务队列里去。