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

查看:84
本文介绍了什么是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 ,具有一些非常小的API差异。



首先,请阅读以下内容:



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



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



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



Ember RunLoop何时启动。它是否依赖于路由器或视图或控制器或其他?



所有基本用户事件(例如键盘事件,鼠标事件等)都将启动运行循环。这样可以确保在将控制返回系统之前,通过捕获(鼠标/键盘/定时器/ etc)事件对绑定属性所做的任何更改都将完整传播到整个Ember的数据绑定系统中。所以,移动鼠标,按键,点击按钮等,都会启动运行循环。



它需要多长时间(我知道这是相当的愚蠢地问和依赖许多事情,但我正在寻找一个一般的想法,或者如果有一个最小或最大的时间,可能需要一个循环)



在任何时候RunLoop会否跟踪通过系统传播所有更改所需的时间,然后在达到最大时限后停止RunLoop;相反,RunLoop将始终运行到完成,并且不会停止,直到所有已过期的计时器被调用,绑定传播,也许他们的绑定传播,等等。显然,需要从单个事件传播的更多变化,RunLoop将要完成的时间越长。这是一个非常不公平的例子,说明如果RunLoop与没有运行循环的另一个框架(Backbone)相比,传播更改可能会陷入僵局: http://jsfiddle.net/jashkenas/CGSd5/ 。道德的故事:RunLoop对于您在Ember中想要做的大部分事情都是非常快的,而且它是Ember的大部分功能所在,但如果您发现自己想要以60帧/秒的速度为Javascript创建30个圈子,那么可能比依靠Ember的RunLoop更好的方法去做。



RunLoop始终执行,或者只是指示执行开始到结束的一段时间,可能无法运行一段时间。



它始终没有被执行 - 它必须在某个时间点将控制权返回到系统,否则你的应用程序将会挂起 - 它与一个运行循环不同一个具有 while(true)的服务器,直到服务器获取关闭信号为止,继续执行... Ember RunLoop没有这样的 while(true),但是仅仅响应于用户/定时器事件而旋转。



如果从一个RunLoop内创建一个视图,是否保证所有内容在循环结束之前将其导入DOM?



我们来看看我们能否弄清楚。从SC到Ember RunLoop的一个重大变化是,不是在 invokeOnce invokeLast 之间来回循环您可以在第一个关于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 定时器] 。 Ember保持他们的代码库非常模块化,并且它们使您的代码以及库中单独部分的代码可以插入更多的队列。在这种情况下,Ember Views库插入 render afterRender 队列,特别是在操作之后队列。我会明白为什么这可能是一秒钟。首先,RunLoop算法:



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




  • 在RunLoop .begin() .end(),只有在Ember中,你需要在 Ember.run 中运行代码,内部调用 begin 结束。 (只有Ember代码库中的内部运行循环代码仍然使用 begin end ,所以你应该坚持使用 Ember.run

  • 调用 end()之后,RunLoop踢到齿轮传播由代码块传递给$ code> Ember.run 函数所做的每一个变化。这包括传播绑定属性的值,将视图更改渲染到DOM等。这些操作(绑定,渲染DOM元素等)的执行顺序由 Ember.run决定。队列数组描述如下:

  • 运行循环将从第一个队列开始,即 sync 。它将通过 Ember.run 代码运行安排在 sync 队列中的所有操作。这些操作本身也可以安排在同一个RunLoop中执行的更多操作,并且由RunLoop确保它执行所有操作,直到所有队列被刷新。它的做法是,在每个队列的末尾,RunLoop将查看所有先前刷新的队列,看看是否已经安排了任何新的动作。如果是这样,它必须从最早的队列的开始处以未执行的调度的动作开始,并刷新队列,继续跟踪其步骤,并在必要时重新开始,直到所有队列完全为空。



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



队列的顺序,包括由Ember Views库添加的队列在这里很重要。请注意, render afterRender 来自 sync ,而动作 sync 队列包含用于传播绑定数据的所有操作。 ( action 之后,仅在Ember源中稀少使用)。基于上述算法,可以确保在RunLoop到达 render 队列时,所有的数据绑定都将完成同步。这是设计的:您不需要在同步数据绑定之前执行渲染DOM元素的昂贵任务,因为这可能需要使用更新的数据重新渲染DOM元素 - 显然是一个非常低效和容易出错的方式排空所有的RunLoop队列。因此,Ember在渲染 render 队列中的DOM元素之前,可以通过所有数据绑定工作进行智能化处理。所以,最后,为了回答你的问题,是的,你可以预期,任何必要的DOM渲染将会在时间 Ember.run 完成。这是一个jsFiddle来演示: http://jsfiddle.net/machty/6p6XJ/328/



有关RunLoop的其他事项



观察者与绑定



重要的是要注意,观察者和绑定,虽然具有类似的功能来响应监视属性中的更改,但在RunLoop的上下文中的行为完全不同。如我们所见,绑定传播被安排在 sync 队列中,最终由RunLoop执行。另一方面,当观察属性更改而不必首先调度到RunLoop队列时,观察者将立即触发。如果一个观察者和一个约束所有的监视同一个属性,观察者将永远被称为比绑定更新的时间的100%。



scheduleOnce Ember.run.once



Ember自动更新模板的效率提升基于以下事实,由于RunLoop,可以将多个相同的RunLoop操作合并(去抖动,如果您将)合并为一个动作。如果您查看 run_loop.js 内部构件,您将看到促进此行为的函数是相关函数 scheduleOnce Em.run.once 。它们之间的区别并不重要,因为知道它们是否存在,以及如何在队列中丢弃重复的操作,以防止在运行循环期间发生大量笨重的浪费计算。



< h3>定时器怎么样?

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



使用同步队列

进一步去抖动

这是一个来自运行循环的代码片段,通过运行循环中所有队列的循环中间。请注意 sync 队列的特殊情况:因为 sync 是一个特别易失的队列,其中数据正在传播调用每个方向调用Ember.beginPropertyChanges()以防止任何观察者被触发,然后调用 Ember.endPropertyChanges 。这是明智的:如果在刷新 sync 队列的过程中,完全可能的是,一个对象上的一个属性将更改多次,然后再依靠其最终值,而你会不要因为每一次变化立即触发观察员而浪费资源。

  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);
}

希望这有帮助。我绝对不得不学习一点点写这个东西,这是一个重点。


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天全站免登陆