什么是 Ember RunLoop,它是如何工作的? [英] What is Ember RunLoop and how does it work?

查看:24
本文介绍了什么是 Ember RunLoop,它是如何工作的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图了解 Ember RunLoop 的工作原理以及它的工作原理.我看过文档,但仍然有很多问题.我有兴趣更好地了解 RunLoop 的工作原理,以便我可以在其命名空间中选择适当的方法,当我不得不推迟某些代码的执行以供以后使用时.

  • Ember RunLoop 什么时候开始.它是否依赖于路由器或视图或控制器或其他东西?
  • 大约需要多长时间(我知道这很愚蠢,而且依赖于很多事情,但我正在寻找一个总体思路,或者是否有一个 runloop 可能需要的最短或最长时间)
  • RunLoop 是一直在执行,还是只是指示从开始到执行结束的一段时间,可能有一段时间不运行.
  • 如果视图是从一个 RunLoop 中创建的,是否可以保证在循环结束时其所有内容都会进入 DOM?

如果这些是非常基本的问题,请原谅我,我认为理解这些将有助于像我这样的新手更好地使用 Ember.

解决方案

更新 10/9/2013: 查看运行循环的交互式可视化:https://machty.s3.amazonaws.com/ember-run-loop-visual/index.html>

更新 5/9/2013:以下所有基本概念仍然是最新的,但截至 此提交,Ember Run Loop 实现已被拆分为一个名为 backburner.js<的单独库./a>,有一些非常小的 API 差异.

首先,请阅读以下内容:

http://blog.sproutcore.com/the-run-loop-part-1/

http://blog.sproutcore.com/the-run-loop-part-2/

它们对 Ember 不是 100% 准确,但 RunLoop 背后的核心概念和动机仍然普遍适用于 Ember;只有一些实现细节不同.但是,关于您的问题:

Ember RunLoop 什么时候开始.它是否依赖于路由器或视图或控制器或其他什么?

所有基本的用户事件(例如键盘事件、鼠标事件等)都会触发运行循环.这保证了在将控制权返回给系统之前,捕获的(鼠标/键盘/计时器/等)事件对绑定属性所做的任何更改都会在 Ember 的数据绑定系统中完全传播.所以,移动你的鼠标,按下一个键,点击一个按钮等等,都会启动运行循环.

大约需要多长时间(我知道这很愚蠢并且依赖于很多事情,但我正在寻找一个总体思路,或者是否有一个 runloop 可能需要的最短或最长时间)

RunLoop 永远不会跟踪将所有更改传播到系统所需的时间,然后在达到最大时间限制后停止 RunLoop;相反,RunLoop 将始终运行到完成,并且不会停止直到所有过期的计时器都被调用、绑定传播,并且可能他们的 绑定传播,等等.显然,需要从单个事件传播的更改越多,RunLoop 完成所需的时间就越长.这是一个(相当不公平的)示例,说明与另一个没有运行循环的框架(Backbone)相比,RunLoop 如何因传播更改而陷入困境:http://jsfiddle.net/jashkenas/CGSd5/ .故事的寓意:RunLoop 对于大多数你想在 Ember 中做的事情来说真的很快,而且它是 Ember 的大部分力量所在,但是如果你发现自己想要用 Javascript 以每秒 60 帧的速度制作 30 个圆圈的动画,那么可能是比依赖 Ember 的 RunLoop 更好的方法.

RunLoop是一直在执行,还是只是指示从开始到执行结束的一段时间,可能有一段时间不运行.

它不会一直执行——它必须在某个时候将控制权返回给系统,否则你的应用程序会挂起——它不同于,比如说,服务器上的运行循环具有 while(true) 并一直持续到服务器收到关闭信号为止...... Ember RunLoop 没有这样的 while(true) 但只是在响应用户/计时器事件.

如果视图是从一个 RunLoop 中创建的,是否可以保证在循环结束时其所有内容都会进入 DOM?

让我们看看我们是否能解决这个问题.从 SC 到 Ember RunLoop 的一个重大变化是,不是在 invokeOnceinvokeLast 之间来回循环(您可以在关于 SproutCore 的第一个链接的图表中看到)RL),Ember 为您提供了一个队列"列表,在运行循环过程中,您可以通过指定操作属于哪个队列来安排操作(在运行循环期间调用的函数)(示例来自源: Ember.run.scheduleOnce('render', bindView, 'rerender');).

如果您查看源代码中的 run_loop.js,您会看到 Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];,但是如果您在 Ember 应用程序的浏览器中打开 JavaScript 调试器并评估 Ember.run.queues,您将获得更完整的队列列表:["sync", "actions", "render", "afterRender", "destroy", "timers"].Ember 保持其代码库非常模块化,并且它们使您的代码以及库的单独部分中的自己的代码插入更多队列成为可能.在这种情况下,Ember 视图库插入 renderafterRender 队列,特别是在 actions 队列之后.我马上就会明白为什么会这样.一、RunLoop算法:

RunLoop 算法与上面 SC 运行循环文章中描述的几乎相同:

  • 你在 RunLoop .begin().end() 之间运行你的代码,只有在 Ember 中你才会想要在 Ember 中运行你的代码.run,它将在内部为您调用 beginend.(只有 Ember 代码库中的内部运行循环代码仍然使用 beginend,所以你应该坚持使用 Ember.run)
  • 在调用 end() 之后,RunLoop 会启动以传播传递给 Ember.run 函数的代码块所做的每一个更改.这包括传播绑定属性的值、将视图更改渲染到 DOM 等.执行这些操作(绑定、渲染 DOM 元素等)的顺序由 Ember.run.queues 决定代码> 上面描述的数组:
  • 运行循环将从第一个队列开始,即 sync.它将运行由 Ember.run 代码安排到 sync 队列中的所有操作.这些操作本身也可能会在同一个 RunLoop 期间安排更多要执行的操作,并且由 RunLoop 确保它执行每个操作,直到所有队列都被刷新.这样做的方式是,在每个队列的末尾,RunLoop 将查看所有以前刷新的队列,看看是否有任何新的操作被安排.如果是这样,它必须从最早的队列的开头开始执行未执行的计划操作并刷新队列,继续跟踪其步骤并在必要时重新开始,直到所有队列都完全清空.

这就是算法的本质.这就是绑定数据通过应用程序传播的方式.您可以预期,一旦 RunLoop 运行完成,所有绑定数据都将完全传播.那么,DOM 元素呢?

队列的顺序,包括 Ember 视图库添加的队列,在这里很重要.请注意,renderafterRender 位于 syncaction 之后.sync 队列包含传播绑定数据的所有操作.(action 之后,在 Ember 源代码中很少使用).基于上述算法,可以保证在 RunLoop 到达 render 队列时,所有数据绑定都将完成同步.这是设计使然:您不希望在同步数据绑定之前执行渲染 DOM 元素的昂贵任务,因为这可能需要使用更新的数据重新渲染 DOM 元素——显然,这是一种非常低效且容易出错的清空所有 RunLoop 队列的方法.因此,Ember 在渲染 render 队列中的 DOM 元素之前,会智能地完成所有数据绑定工作.

所以,最后,要回答您的问题,是的,您可以预期在 Ember.run 完成时任何必要的 DOM 渲染都将发生.这里有一个 jsFiddle 来演示:http://jsfiddle.net/machty/6p6XJ/328/

关于 RunLoop 的其他须知

观察者与绑定

需要注意的是,观察者和绑定虽然具有响应观察"属性变化的类似功能,但在 RunLoop 上下文中的行为完全不同.正如我们所见,绑定传播被安排到 sync 队列中,最终由 RunLoop 执行.另一方面,当被监视的属性发生变化时,观察者会立即触发,而不必先调度到 RunLoop 队列中.如果观察者和绑定都观察"相同的属性,观察者将始终在绑定更新之前 100% 的时间被调用.

scheduleOnceEmber.run.once

Ember 自动更新模板的一大效率提升是基于这样一个事实,即多亏了 RunLoop,多个相同的 RunLoop 动作可以合并(去抖动",如果你愿意的话)为一个动作.如果您查看 run_loop.js 内部结构,您会看到促进这种行为的函数是相关函数 scheduleOnceEm.run.once.它们之间的区别并不像知道它们存在那么重要,以及它们如何丢弃队列中的重复动作以防止在运行循环期间进行大量臃肿、浪费的计算.

定时器呢?

即使 'timers' 是上面列出的默认队列之一,Ember 只在他们的 RunLoop 测试用例中引用了这个队列.根据上述文章中关于定时器是最后触发的一些描述,似乎在 SproutCore 时代会使用这样的队列.在 Ember 中,不使用 timers 队列.相反,RunLoop 可以由内部管理的 setTimeout 事件启动(请参阅 invokeLaterTimers 函数),它足够智能,可以遍历所有现有的计时器,触发所有那些已过期的事件,确定最早的未来计时器,并仅为该事件设置一个内部 setTimeout,这将在它触发时再次启动 RunLoop.这种方法比让每个计时器调用 setTimeout 并唤醒自己更有效,因为在这种情况下,只需要进行一次 setTimeout 调用,并且 RunLoop 足够智能,可以触发所有可能同时关闭的不同计时器时间.

使用 sync 队列进一步去抖动

这是运行循环中的一个片段,位于运行循环中所有队列的循环中间.请注意 sync 队列的特殊情况:因为 sync 是一个特别易变的队列,其中数据向各个方向传播,Ember.beginPropertyChanges() 被调用以防止任何观察者被触发,然后调用 Ember.endPropertyChanges.这是明智的:如果在刷新 sync 队列的过程中,对象上的属性完全有可能在保留其最终值之前多次更改,并且您不想浪费资源通过在每次更改时立即触发观察者.

if (queueName === 'sync'){日志 = Ember.LOG_BINDINGS;如果(日志){Ember.Logger.log('开始:刷新同步队列');}Ember.beginPropertyChanges();Ember.tryFinally(tryable, Ember.endPropertyChanges);如果(日志){Ember.Logger.log('结束:刷新同步队列');}}别的{forEach.call(queue, iter);}

希望这会有所帮助.为了写这个东西,我肯定需要学习很多东西,这就是重点.

I am trying to understand how Ember RunLoop works and what makes it tick. I have looked at the documentation, but still have many questions about it. I am interested in understanding better how RunLoop works so I can choose appropriate method within its name space, when I have to defer execution of some code for later time.

  • When does Ember RunLoop start. Is it dependant on Router or Views or Controllers or something else?
  • how long does it approximately take (I know this is rather silly to asks and dependant on many things but I am looking for a general idea, or maybe if there is a minimum or maximum time a runloop may take)
  • Is RunLoop being executed at all times, or is it just indicating a period of time from beginning to end of execution and may not run for some time.
  • If a view is created from within one RunLoop, is it guaranteed that all its content will make it into the DOM by the time the loop ends?

Forgive me if these are very basic questions, I think understanding these will help noobs like me use Ember better.

解决方案

Update 10/9/2013: Check out this interactive visualization of the run loop: https://machty.s3.amazonaws.com/ember-run-loop-visual/index.html

Update 5/9/2013: all the basic concepts below are still up to date, but as of this commit, the Ember Run Loop implementation has been split off into a separate library called backburner.js, with some very minor API differences.

First off, read these:

http://blog.sproutcore.com/the-run-loop-part-1/

http://blog.sproutcore.com/the-run-loop-part-2/

They're not 100% accurate to Ember, but the core concepts and motivation behind the RunLoop still generally apply to Ember; only some implementation details differ. But, on to your questions:

When does Ember RunLoop start. Is it dependant on Router or Views or Controllers or something else?

All of the basic user events (e.g. keyboard events, mouse events, etc) will fire up the run loop. This guarantees that whatever changes made to bound properties by the captured (mouse/keyboard/timer/etc) event are fully propagated throughout Ember's data-binding system before returning control back to the system. So, moving your mouse, pressing a key, clicking a button, etc., all launch the run loop.

how long does it approximately take (I know this is rather silly to asks and dependant on many things but I am looking for a general idea, or maybe if there is a minimum or maximum time a runloop may take)

At no point will the RunLoop ever keep track of how much time it's taking to propagate all the changes through the system and then halt the RunLoop after reaching a maximum time limit; rather, the RunLoop will always run to completion, and won't stop until all the expired timers have been called, bindings propagated, and perhaps their bindings propagated, and so on. Obviously, the more changes that need to be propagated from a single event, the longer the RunLoop will take to finish. Here's a (pretty unfair) example of how the RunLoop can get bogged down with propagating changes compared to another framework (Backbone) that doesn't have a run loop: http://jsfiddle.net/jashkenas/CGSd5/ . Moral of the story: the RunLoop's really fast for most things you'd ever want to do in Ember, and it's where much of Ember's power lies, but if you find yourself wanting to animate 30 circles with Javascript at 60 frames per second, there might be better ways to go about it than relying on Ember's RunLoop.

Is RunLoop being executed at all times, or is it just indicating a period of time from beginning to end of execution and may not run for some time.

It is not executed at all times -- it has to return control back to the system at some point or else your app would hang -- it's different from, say, a run loop on a server that has a while(true) and goes on for infinity until the server gets a signal to shut down... the Ember RunLoop has no such while(true) but is only spun up in response to user/timer events.

If a view is created from within one RunLoop, is it guaranteed that all its content will make it into the DOM by the time the loop ends?

Let's see if we can figure that out. One of the big changes from SC to Ember RunLoop is that, instead of looping back and forth between invokeOnce and invokeLast (which you see in the diagram in the first link about SproutCore's RL), Ember provides you a list of 'queues' that, in the course of a run loop, you can schedule actions (functions to be called during the run loop) to by specifying which queue the action belongs in (example from the source: Ember.run.scheduleOnce('render', bindView, 'rerender');).

If you look at run_loop.js in the source code, you see Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];, yet if you open your JavaScript debugger in the browser in an Ember app and evaluate Ember.run.queues, you get a fuller list of queues: ["sync", "actions", "render", "afterRender", "destroy", "timers"]. Ember keeps their codebase pretty modular, and they make it possible for your code, as well as its own code in a separate part of the library, to insert more queues. In this case, the Ember Views library inserts render and afterRender queues, specifically after the actions queue. I'll get to why that might be in a second. First, the RunLoop algorithm:

The RunLoop algorithm is pretty much the same as described in the SC run loop articles above:

  • You run your code between RunLoop .begin() and .end(), only in Ember you'll want to instead run your code within Ember.run, which will internally call begin and end for you. (Only internal run loop code in the Ember code base still uses begin and end, so you should just stick with Ember.run)
  • After end() is called, the RunLoop then kicks into gear to propagate every single change made by the chunk of code passed to the Ember.run function. This includes propagating the values of bound properties, rendering view changes to the DOM, etc etc. The order in which these actions (binding, rendering DOM elements, etc) are performed is determined by the Ember.run.queues array described above:
  • The run loop will start off on the first queue, which is sync. It'll run all of the actions that were scheduled into the sync queue by the Ember.run code. These actions may themselves also schedule more actions to be performed during this same RunLoop, and it's up to the RunLoop to make sure it performs every action until all the queues are flushed. The way it does this is, at the end of every queue, the RunLoop will look through all the previously flushed queues and see if any new actions have been scheduled. If so, it has to start at the beginning of the earliest queue with unperformed scheduled actions and flush out the queue, continuing to trace its steps and start over when necessary until all of the queues are completely empty.

That's the essence of the algorithm. That's how bound data gets propagated through the app. You can expect that once a RunLoop runs to completion, all of the bound data will be fully propagated. So, what about DOM elements?

The order of the queues, including the ones added in by the Ember Views library, is important here. Notice that render and afterRender come after sync, and action. The sync queue contains all the actions for propagating bound data. (action, after that, is only sparsely used in the Ember source). Based on the above algorithm, it is guaranteed that by the time the RunLoop gets to the render queue, all of the data-bindings will have finished synchronizing. This is by design: you wouldn't want to perform the expensive task of rendering DOM elements before sync'ing the data-bindings, since that would likely require re-rendering DOM elements with updated data -- obviously a very inefficient and error-prone way of emptying all of the RunLoop queues. So Ember intelligently blasts through all the data-binding work it can before rendering the DOM elements in the render queue.

So, finally, to answer your question, yes, you can expect that any necessary DOM renderings will have taken place by the time Ember.run finishes. Here's a jsFiddle to demonstrate: http://jsfiddle.net/machty/6p6XJ/328/

Other things to know about the RunLoop

Observers vs. Bindings

It's important to note that Observers and Bindings, while having the similar functionality of responding to changes in a "watched" property, behave totally differently in the context of a RunLoop. Binding propagation, as we've seen, gets scheduled into the sync queue to eventually be executed by the RunLoop. Observers, on the other hand, fire immediately when the watched property changes without having to be first scheduled into a RunLoop queue. If an Observer and a binding all "watch" the same property, the observer will always be called 100% of the time earlier than the binding will be updated.

scheduleOnce and Ember.run.once

One of the big efficiency boosts in Ember's auto-updating templates is based on the fact that, thanks to the RunLoop, multiple identical RunLoop actions can be coalesced ("debounced", if you will) into a single action. If you look into the run_loop.js internals, you'll see the functions that facilitate this behavior are the related functions scheduleOnce and Em.run.once. The difference between them isn't so important as knowing they exist, and how they can discard duplicate actions in queue to prevent a lot of bloated, wasteful calculation during the run loop.

What about timers?

Even though 'timers' is one of the default queues listed above, Ember only makes reference to the queue in their RunLoop test cases. It seems that such a queue would have been used in the SproutCore days based on some of the descriptions from the above articles about timers being the last thing to fire. In Ember, the timers queue isn't used. Instead, the RunLoop can be spun up by an internally managed setTimeout event (see the invokeLaterTimers function), which is intelligent enough to loop through all the existing timers, fire all the ones that have expired, determine the earliest future timer, and set an internal setTimeout for that event only, which will spin up the RunLoop again when it fires. This approach is more efficient than having each timer call setTimeout and wake itself up, since in this case, only one setTimeout call needs to be made, and the RunLoop is smart enough to fire all the different timers that might be going off at the same time.

Further debouncing with the sync queue

Here's a snippet from the run loop, in the middle of a loop through all the queues in the run loop. Note the special case for the sync queue: because sync is a particularly volatile queue, in which data is being propagated in every direction, Ember.beginPropertyChanges() is called to prevent any observers from being fired, followed by a call to Ember.endPropertyChanges. This is wise: if in the course of flushing the sync queue, it's entirely possible that a property on an object will change multiple times before resting on its final value, and you wouldn't want to waste resources by immediately firing observers per every single change.

if (queueName === 'sync') 
{
    log = Ember.LOG_BINDINGS;

    if (log) 
    {
        Ember.Logger.log('Begin: Flush Sync Queue');
    }

    Ember.beginPropertyChanges();
    Ember.tryFinally(tryable, Ember.endPropertyChanges);

    if (log) 
    { 
        Ember.Logger.log('End: Flush Sync Queue'); 
    }
} 
else 
{
   forEach.call(queue, iter);
}

Hope this helps. I definitely had to learn quite a bit just to write this thing, which was kind of the point.

这篇关于什么是 Ember RunLoop,它是如何工作的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆