¿Qué es Ember RunLoop y cómo funciona?

96

Estoy tratando de entender cómo funciona Ember RunLoop y qué lo hace funcionar. He mirado la documentación , pero todavía tengo muchas preguntas al respecto. Estoy interesado en comprender mejor cómo funciona RunLoop para poder elegir el método apropiado dentro de su espacio de nombres, cuando tengo que posponer la ejecución de algún código para más adelante.

  • ¿Cuándo comienza Ember RunLoop? ¿Depende del enrutador, las vistas, los controladores o algo más?
  • cuánto tiempo toma aproximadamente (sé que es bastante tonto preguntar y depende de muchas cosas, pero estoy buscando una idea general, o tal vez si hay un tiempo mínimo o máximo que puede tomar un runloop)
  • ¿RunLoop se está ejecutando en todo momento, o simplemente indica un período de tiempo desde el principio hasta el final de la ejecución y puede que no se ejecute durante algún tiempo?
  • Si se crea una vista desde dentro de un RunLoop, ¿se garantiza que todo su contenido ingresará al DOM cuando finalice el bucle?

Perdóname si estas son preguntas muy básicas, creo que entenderlas ayudará a los novatos como yo a usar mejor Ember.

Aras
fuente
5
No hay buenos documentos sobre el ciclo de ejecución. Voy a intentar armar una breve presentación de diapositivas esta semana.
Luke Melia
2
@LukeMelia esta pregunta todavía necesita desesperadamente su atención y parece que otras personas están buscando la misma información. Sería maravilloso, si tiene la oportunidad, compartir sus ideas sobre RunLoop.
Aras

Respuestas:

199

Actualización 9/10/2013: consulte esta visualización interactiva del ciclo de ejecución: https://machty.s3.amazonaws.com/ember-run-loop-visual/index.html

Actualización 9/5/2013: todos los conceptos básicos a continuación aún están actualizados, pero a partir de esta confirmación , la implementación de Ember Run Loop se ha dividido en una biblioteca separada llamada backburner.js , con algunas diferencias de API muy pequeñas.

En primer lugar, lea estos:

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

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

No son 100% precisos para Ember, pero los conceptos centrales y la motivación detrás de RunLoop todavía se aplican generalmente a Ember; solo difieren algunos detalles de implementación. Pero, a tus preguntas:

¿Cuándo comienza Ember RunLoop? ¿Depende del enrutador, las vistas, los controladores o algo más?

Todos los eventos de usuario básicos (por ejemplo, eventos de teclado, eventos de mouse, etc.) activarán el ciclo de ejecución. Esto garantiza que cualquier cambio realizado en las propiedades vinculadas por el evento capturado (mouse / teclado / temporizador / etc.) se propague por completo a través del sistema de enlace de datos de Ember antes de devolver el control al sistema. Entonces, moviendo el mouse, presionando una tecla, haciendo clic en un botón, etc., todos inician el ciclo de ejecución.

cuánto tiempo toma aproximadamente (sé que es bastante tonto preguntar y depende de muchas cosas, pero estoy buscando una idea general, o tal vez si hay un tiempo mínimo o máximo que puede tomar un runloop)

En ningún momento RunLoop hará un seguimiento de cuánto tiempo se tarda en propagar todos los cambios a través del sistema y luego detendrá RunLoop después de alcanzar un límite de tiempo máximo; más bien, RunLoop siempre se ejecutará hasta completarse y no se detendrá hasta que se hayan llamado a todos los temporizadores caducados, se hayan propagado los enlaces, y tal vez se hayan propagado sus enlaces, etc. Obviamente, cuantos más cambios deban propagarse desde un solo evento, más tardará RunLoop en finalizar. Aquí hay un ejemplo (bastante injusto) de cómo RunLoop puede atascarse con la propagación de cambios en comparación con otro marco (Backbone) que no tiene un bucle de ejecución: http://jsfiddle.net/jashkenas/CGSd5/. Moraleja de la historia: el RunLoop es realmente rápido para la mayoría de las cosas que querrías hacer en Ember, y es donde reside gran parte del poder de Ember, pero si quieres animar 30 círculos con Javascript a 60 fotogramas por segundo, ahí está podría ser una mejor manera de hacerlo que confiar en RunLoop de Ember.

¿RunLoop se está ejecutando en todo momento, o simplemente indica un período de tiempo desde el principio hasta el final de la ejecución y puede que no se ejecute durante algún tiempo?

No se ejecuta en todo momento; tiene que devolver el control al sistema en algún momento o de lo contrario su aplicación se bloqueará; es diferente de, por ejemplo, un ciclo de ejecución en un servidor que tiene un while(true)y continúa hasta el infinito hasta el servidor recibe una señal para apagarse ... el Ember RunLoop no tiene tal, while(true)pero solo se activa en respuesta a eventos de usuario / temporizador.

Si se crea una vista desde dentro de un RunLoop, ¿se garantiza que todo su contenido ingresará al DOM cuando finalice el bucle?

Veamos si podemos resolver eso. Uno de los grandes cambios de SC a Ember RunLoop es que, en lugar de ir y venir entre invokeOncey invokeLast(que se ve en el diagrama en el primer enlace sobre el RL de SproutCore), Ember le proporciona una lista de 'colas' que, en el Durante el curso de un ciclo de ejecución, puede programar acciones (funciones que se llamarán durante el ciclo de ejecución) especificando a qué cola pertenece la acción (ejemplo de la fuente:) Ember.run.scheduleOnce('render', bindView, 'rerender');.

Si nos fijamos en run_loop.jsel código fuente, se ve Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];, sin embargo, si se abre el depurador de JavaScript en el navegador en una aplicación Ember y evaluar Ember.run.queues, se obtiene una lista más completa de colas: ["sync", "actions", "render", "afterRender", "destroy", "timers"]. Ember mantiene su base de código bastante modular, y hacen posible que su código, así como su propio código en una parte separada de la biblioteca, inserte más colas. En este caso, la biblioteca Ember Views inserta rendery afterRenderpone en cola, específicamente después de la actionscola. Llegaré a por qué eso podría ser en un segundo. Primero, el algoritmo RunLoop:

El algoritmo RunLoop es prácticamente el mismo que se describe en los artículos anteriores del ciclo de ejecución de SC:

  • Ejecuta su código entre RunLoop .begin()y .end(), solo en Ember, querrá ejecutar su código dentro Ember.run, que llamará internamente beginy endpor usted. (Solo el código de ciclo de ejecución interno en la base del código Ember todavía usa beginy end, por lo que debe seguir con Ember.run)
  • Después de que end()se llama, RunLoop se pone en marcha para propagar cada cambio realizado por el fragmento de código pasado a la Ember.runfunción. Esto incluye la propagación de los valores de las propiedades vinculadas, la representación de cambios de vista en el DOM, etc. El orden en el que se realizan estas acciones (vinculación, representación de elementos DOM, etc.) está determinado por la Ember.run.queuesmatriz descrita anteriormente:
  • El ciclo de ejecución comenzará en la primera cola, que es sync. Ejecutará todas las acciones programadas en la synccola por el Ember.runcódigo. Estas acciones también pueden programar más acciones para realizar durante este mismo RunLoop, y depende del RunLoop asegurarse de que realiza todas las acciones hasta que se eliminen todas las colas. La forma en que lo hace es que, al final de cada cola, RunLoop revisará todas las colas previamente descargadas y verá si se han programado nuevas acciones. Si es así, tiene que comenzar al principio de la cola más temprana con acciones programadas no realizadas y vaciar la cola, continuar rastreando sus pasos y comenzar de nuevo cuando sea necesario hasta que todas las colas estén completamente vacías.

Esa es la esencia del algoritmo. Así es como se propagan los datos vinculados a través de la aplicación. Puede esperar que una vez que RunLoop se ejecute hasta su finalización, todos los datos vinculados se propagarán por completo. Entonces, ¿qué pasa con los elementos DOM?

El orden de las colas, incluidas las agregadas por la biblioteca Ember Views, es importante aquí. Note eso rendery afterRendervenga después sync, y action. La synccola contiene todas las acciones para propagar datos enlazados. ( action, después de eso, solo se usa escasamente en la fuente Ember). Basado en el algoritmo anterior, se garantiza que para cuando RunLoop llegue a la rendercola, todas las vinculaciones de datos habrán terminado de sincronizarse. Esto es por diseño: no querrá realizar la costosa tarea de renderizar elementos DOM antessincronizar los enlaces de datos, ya que eso probablemente requeriría volver a renderizar los elementos DOM con datos actualizados, obviamente una forma muy ineficiente y propensa a errores de vaciar todas las colas de RunLoop. Por lo tanto, Ember realiza de manera inteligente todo el trabajo de enlace de datos que puede antes de representar los elementos DOM en la rendercola.

Entonces, finalmente, para responder a su pregunta, sí, puede esperar que las representaciones DOM necesarias hayan tenido lugar para cuando Ember.runfinalice el tiempo . Aquí hay un jsFiddle para demostrar: http://jsfiddle.net/machty/6p6XJ/328/

Otras cosas que debe saber sobre RunLoop

Observadores contra ataduras

Es importante tener en cuenta que los observadores y enlaces, aunque tienen la funcionalidad similar de responder a los cambios en una propiedad "observada", se comportan de manera totalmente diferente en el contexto de un RunLoop. La propagación de enlaces, como hemos visto, se programa en la synccola para que RunLoop la ejecute finalmente. Los observadores, por otro lado, disparan inmediatamente cuando cambia la propiedad observada sin tener que ser programados primero en una cola RunLoop. Si un observador y un enlace "observan" la misma propiedad, el observador siempre será llamado el 100% de las veces antes de que se actualice el enlace.

scheduleOnce y Ember.run.once

Uno de los grandes aumentos de eficiencia en las plantillas de actualización automática de Ember se basa en el hecho de que, gracias a RunLoop, múltiples acciones RunLoop idénticas se pueden unir ("eliminar rebotes", si se quiere) en una sola acción. Si observa los run_loop.jsaspectos internos, verá que las funciones que facilitan este comportamiento son las funciones relacionadas scheduleOncey Em.run.once. La diferencia entre ellos no es tan importante como saber que existen y cómo pueden descartar acciones duplicadas en la cola para evitar una gran cantidad de cálculos inflados y derrochadores durante el ciclo de ejecución.

¿Y los temporizadores?

Aunque 'temporizadores' es una de las colas predeterminadas enumeradas anteriormente, Ember solo hace referencia a la cola en sus casos de prueba RunLoop. Parece que tal cola se habría utilizado en los días de SproutCore según algunas de las descripciones de los artículos anteriores sobre los temporizadores como lo último en dispararse. En Ember, la timerscola no se usa. En su lugar, RunLoop puede activarse mediante un setTimeoutevento administrado internamente (consulte el solo para ese evento, que activará RunLoop nuevamente cuando se active. Este enfoque es más eficiente que hacer que cada temporizador llame a setTimeout y se despierte a sí mismo, ya que en En este caso, solo se necesita hacer una llamada setTimeout, y RunLoop es lo suficientemente inteligente como para disparar todos los diferentes temporizadores que podrían estar sonando al mismo tiempo.invokeLaterTimers función), que es lo suficientemente inteligente como para recorrer todos los temporizadores existentes, activar todos los que han expirado, determinar el temporizador futuro más temprano y establecer un temporizador interno.setTimeout

Más eliminación de rebotes con la synccola

Aquí hay un fragmento del ciclo de ejecución, en medio de un ciclo a través de todas las colas en el ciclo de ejecución. Tenga en cuenta el caso especial de la synccola: porque synces una cola particularmente volátil, en la que los datos se propagan en todas las direcciones, Ember.beginPropertyChanges()se llama para evitar que se dispare cualquier observador, seguido de una llamada a Ember.endPropertyChanges. Esto es sabio: si en el curso de vaciar la synccola, es muy posible que una propiedad en un objeto cambie varias veces antes de descansar en su valor final, y no querrá desperdiciar recursos disparando inmediatamente observadores por cada cambio. .

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

Espero que esto ayude. Definitivamente tuve que aprender bastante solo para escribir esto, que era el punto.

Alexander Wallace Matchneer
fuente
3
¡Gran reseña! Escuché rumores de que lo de "los observadores disparan instantáneamente" podría cambiar en algún momento, para retrasarlos como si fueran ataduras.
Jo Liss
@JoLiss sí, siento que he oído hablar de eso durante unos meses ... no estoy seguro de si o cuándo llegará.
Alexander Wallace Matchneer
1
Brendan Briggs hizo una gran presentación sobre Run Loop en la reunión de Ember.js NYC de enero de 2014. Video aquí: youtube.com/watch?v=iCZUKFNXA0k
Luke Melia
1
Esta respuesta fue el mejor recurso que encontré sobre Ember Run Loop, ¡muy buen trabajo! Recientemente publiqué un extenso tutorial sobre Run Loop basado en su trabajo que espero describa aún más detalles de ese mecanismo. Disponible aquí en.netguru.co/ember-ebook-form
Kuba Niechciał