Qu'est-ce qu'Ember RunLoop et comment ça marche?

96

J'essaie de comprendre comment fonctionne Ember RunLoop et ce qui le motive. J'ai regardé la documentation , mais j'ai encore de nombreuses questions à ce sujet. Je suis intéressé à mieux comprendre le fonctionnement de RunLoop afin de pouvoir choisir la méthode appropriée dans son espace de nom, lorsque je dois reporter l'exécution d'un code à une date ultérieure.

  • Quand Ember RunLoop démarre-t-il. Dépend-il du routeur ou des vues ou des contrôleurs ou de quelque chose d'autre?
  • combien de temps cela prend-il approximativement (je sais que c'est plutôt idiot de demander et dépend de beaucoup de choses mais je cherche une idée générale, ou peut-être s'il y a un temps minimum ou maximum qu'une boucle d'exécution peut prendre)
  • RunLoop est-il exécuté à tout moment ou indique-t-il simplement une période allant du début à la fin de l'exécution et peut ne pas s'exécuter pendant un certain temps.
  • Si une vue est créée à partir d'un RunLoop, est-il garanti que tout son contenu sera intégré dans le DOM à la fin de la boucle?

Pardonnez-moi si ce sont des questions très basiques, je pense que les comprendre aidera les noobs comme moi à mieux utiliser Ember.

Aras
la source
5
Il n'y a pas de bons documents sur la boucle d'exécution. Je vais essayer de créer une courte présentation de diapositives à ce sujet cette semaine.
Luke Melia
2
@LukeMelia, cette question a encore désespérément besoin de votre attention et il semble que d'autres personnes recherchent les mêmes informations. Ce serait merveilleux, si vous en avez l'occasion, de partager vos idées sur RunLoop.
Aras

Réponses:

199

Mise à jour 10/9/2013: Découvrez cette visualisation interactive de la boucle d'exécution: https://machty.s3.amazonaws.com/ember-run-loop-visual/index.html

Mise à jour du 09/05/2013: tous les concepts de base ci-dessous sont toujours à jour, mais à partir de cette validation , l'implémentation d'Ember Run Loop a été divisée en une bibliothèque séparée appelée backburner.js , avec quelques différences d'API très mineures.

Tout d'abord, lisez ceci:

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

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

Ils ne sont pas 100% précis à Ember, mais les concepts de base et la motivation derrière le RunLoop s'appliquent toujours généralement à Ember; seuls quelques détails d'implémentation diffèrent. Mais, passons à vos questions:

Quand Ember RunLoop démarre-t-il. Dépend-il du routeur ou des vues ou des contrôleurs ou de quelque chose d'autre?

Tous les événements utilisateur de base (par exemple les événements de clavier, les événements de souris, etc.) déclencheront la boucle d'exécution. Cela garantit que toutes les modifications apportées aux propriétés liées par l'événement capturé (souris / clavier / minuterie / etc.) sont entièrement propagées dans le système de liaison de données d'Ember avant de retourner le contrôle au système. Ainsi, déplacer votre souris, appuyer sur une touche, cliquer sur un bouton, etc., tous lancent la boucle d'exécution.

combien de temps cela prend-il approximativement (je sais que c'est plutôt idiot de demander et dépend de beaucoup de choses mais je cherche une idée générale, ou peut-être s'il y a un temps minimum ou maximum qu'une boucle d'exécution peut prendre)

A aucun moment, le RunLoop ne gardera une trace du temps qu'il faut pour propager tous les changements à travers le système, puis arrêtera le RunLoop après avoir atteint une limite de temps maximum; au contraire, le RunLoop fonctionnera toujours jusqu'à la fin, et ne s'arrêtera pas tant que tous les minuteurs expirés n'auront pas été appelés, les liaisons propagées, et peut-être leurs liaisons propagées, et ainsi de suite. Évidemment, plus il y a de changements à propager à partir d'un seul événement, plus le RunLoop mettra du temps à se terminer. Voici un exemple (assez injuste) de la façon dont le RunLoop peut s'enliser avec la propagation des changements par rapport à un autre framework (Backbone) qui n'a pas de boucle d'exécution: http://jsfiddle.net/jashkenas/CGSd5/. Morale de l'histoire: le RunLoop est très rapide pour la plupart des choses que vous voudriez faire dans Ember, et c'est là que réside une grande partie du pouvoir d'Ember, mais si vous vous trouvez à vouloir animer 30 cercles avec Javascript à 60 images par seconde, là pourrait être une meilleure façon de procéder que de compter sur le RunLoop d'Ember.

RunLoop est-il exécuté à tout moment ou indique-t-il simplement une période allant du début à la fin de l'exécution et peut ne pas s'exécuter pendant un certain temps.

Il n'est pas exécuté à tout moment - il doit renvoyer le contrôle au système à un moment donné, sinon votre application se bloquerait - c'est différent, par exemple, d'une boucle d'exécution sur un serveur qui a un while(true)et continue pendant l'infini jusqu'à le serveur reçoit un signal d'arrêt ... l'Ember RunLoop n'en a pas, while(true)mais n'est lancé qu'en réponse aux événements utilisateur / minuterie.

Si une vue est créée à partir d'un RunLoop, est-il garanti que tout son contenu sera intégré dans le DOM à la fin de la boucle?

Voyons si nous pouvons comprendre cela. L' un des grands changements de SC à Ember runloop est que, au lieu de boucle et - vient entre invokeOnceet invokeLast(que vous voyez dans le diagramme dans le premier lien sur la RL SproutCore), Ember vous fournit une liste des « files d' attente » qui, dans le au cours d'une boucle d'exécution, vous pouvez planifier des actions (fonctions à appeler pendant la boucle d'exécution) en spécifiant à quelle file d'attente l'action appartient (exemple de la source:) Ember.run.scheduleOnce('render', bindView, 'rerender');.

Si vous regardez run_loop.jsdans le code source, vous voyez Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];, mais si vous ouvrez votre débogueur JavaScript dans le navigateur dans une application Ember et d' évaluer Ember.run.queues, vous obtenez une liste plus complète des files d' attente: ["sync", "actions", "render", "afterRender", "destroy", "timers"]. Ember garde sa base de code assez modulaire et permet à votre code, ainsi qu'à son propre code dans une partie distincte de la bibliothèque, d'insérer plus de files d'attente. Dans ce cas, la Ember Vues inserts bibliothèque renderet les afterRenderfiles d' attente, en particulier après la actionsfile d' attente. Je vais comprendre pourquoi cela pourrait être dans une seconde. Tout d'abord, l'algorithme RunLoop:

L'algorithme RunLoop est à peu près le même que celui décrit dans les articles de boucle d'exécution SC ci-dessus:

  • Vous exécutez votre code entre RunLoop .begin()et .end(), seulement dans Ember, vous voudrez plutôt exécuter votre code à l'intérieur Ember.run, qui appellera en interne beginet endpour vous. (Seul le code de boucle d'exécution interne dans la base de code Ember utilise toujours beginet end, vous devez donc vous en tenir à Ember.run)
  • Après avoir end()été appelé, le RunLoop passe à la vitesse supérieure pour propager chaque modification apportée par le morceau de code passé à la Ember.runfonction. Cela inclut la propagation des valeurs des propriétés liées, le rendu des modifications de vue dans le DOM, etc. L'ordre dans lequel ces actions (liaison, rendu des éléments DOM, etc.) sont effectuées est déterminé par le Ember.run.queuestableau décrit ci-dessus:
  • La boucle d'exécution démarre sur la première file d'attente, qui est sync. Il exécutera toutes les actions planifiées dans la syncfile d'attente par le Ember.runcode. Ces actions peuvent elles-mêmes planifier d'autres actions à effectuer au cours de ce même RunLoop, et c'est au RunLoop de s'assurer qu'il exécute chaque action jusqu'à ce que toutes les files d'attente soient vidées. Pour ce faire, à la fin de chaque file d'attente, le RunLoop examinera toutes les files d'attente précédemment vidées et verra si de nouvelles actions ont été planifiées. Si tel est le cas, il doit démarrer au début de la première file d'attente avec des actions planifiées non exécutées et vider la file d'attente, continuer à suivre ses étapes et recommencer si nécessaire jusqu'à ce que toutes les files d'attente soient complètement vides.

C'est l'essence de l'algorithme. C'est ainsi que les données liées sont propagées via l'application. Vous pouvez vous attendre qu'une fois qu'un RunLoop s'exécute, toutes les données liées seront entièrement propagées. Alors, qu'en est-il des éléments DOM?

L'ordre des files d'attente, y compris celles ajoutées par la bibliothèque Ember Views, est important ici. Remarquez cela renderet afterRendervenez après sync, et action. La syncfile d'attente contient toutes les actions de propagation des données liées. ( action, après cela, n'est que rarement utilisé dans la source Ember). Sur la base de l'algorithme ci-dessus, il est garanti qu'au moment où le RunLoop arrive dans la renderfile d'attente, toutes les liaisons de données auront terminé la synchronisation. C'est par conception: vous ne voudriez pas effectuer la tâche coûteuse de rendu des éléments DOM avantsynchroniser les liaisons de données, car cela nécessiterait probablement de restituer les éléments DOM avec des données mises à jour - évidemment une manière très inefficace et sujette aux erreurs de vider toutes les files d'attente RunLoop. Ainsi, Ember analyse intelligemment tout le travail de liaison de données qu'il peut avant de rendre les éléments DOM dans la renderfile d'attente.

Donc, enfin, pour répondre à votre question, oui, vous pouvez vous attendre à ce que tous les rendus DOM nécessaires aient eu lieu à la Ember.runfin. Voici un jsFiddle à démontrer: http://jsfiddle.net/machty/6p6XJ/328/

Autres choses à savoir sur le RunLoop

Observateurs vs liaisons

Il est important de noter que les observateurs et les liaisons, tout en ayant la fonctionnalité similaire de répondre aux changements dans une propriété «surveillée», se comportent totalement différemment dans le contexte d'un RunLoop. La propagation de liaison, comme nous l'avons vu, est planifiée dans la syncfile d'attente pour être finalement exécutée par le RunLoop. Les observateurs, en revanche, se déclenchent immédiatement lorsque la propriété surveillée change sans avoir à être programmés au préalable dans une file d'attente RunLoop. Si un observateur et une liaison «surveillent» tous la même propriété, l'observateur sera toujours appelé 100% du temps avant que la liaison ne soit mise à jour.

scheduleOnce et Ember.run.once

Un des gros gains d'efficacité dans les modèles de mise à jour automatique d'Ember est basé sur le fait que, grâce au RunLoop, plusieurs actions RunLoop identiques peuvent être fusionnées ("déboncées", si vous voulez) en une seule action. Si vous regardez dans les éléments run_loop.jsinternes, vous verrez que les fonctions qui facilitent ce comportement sont les fonctions associées scheduleOnceet Em.run.once. La différence entre eux n'est pas si importante que de savoir qu'ils existent, et comment ils peuvent ignorer les actions en double dans la file d'attente pour éviter beaucoup de calculs gonflés et inutiles pendant la boucle d'exécution.

Et les minuteries?

Même si 'timers' est l'une des files d'attente par défaut répertoriées ci-dessus, Ember fait uniquement référence à la file d'attente dans leurs cas de test RunLoop. Il semble qu'une telle file d'attente aurait été utilisée à l'époque de SproutCore sur la base de certaines des descriptions des articles ci-dessus concernant les minuteries étant la dernière chose à tirer. Dans Ember, la timersfile d'attente n'est pas utilisée. Au lieu de cela, le RunLoop peut être lancé par un setTimeoutévénement géré en interne (voir la invokeLaterTimersfonction), qui est suffisamment intelligent pour parcourir tous les minuteurs existants, déclencher tous ceux qui ont expiré, déterminer le premier minuteur futur et définir unsetTimeoutpour cet événement uniquement, ce qui fera redémarrer le RunLoop lorsqu'il se déclenchera. Cette approche est plus efficace que d'avoir chaque appel de minuterie setTimeout et se réveiller, car dans ce cas, un seul appel setTimeout doit être effectué, et le RunLoop est suffisamment intelligent pour déclencher tous les différents minuteurs qui pourraient se déclencher en même temps. temps.

Plus de rebond avec la syncfile d'attente

Voici un extrait de la boucle d'exécution, au milieu d'une boucle à travers toutes les files d'attente de la boucle d'exécution. Notez le cas particulier de la syncfile d'attente: parce syncqu'une file d'attente particulièrement volatile, dans laquelle les données sont propagées dans toutes les directions, Ember.beginPropertyChanges()est appelée pour empêcher le déclenchement de tout observateur, suivi d'un appel à Ember.endPropertyChanges. C'est sage: si au cours du vidage de la syncfile d'attente, il est tout à fait possible qu'une propriété sur un objet change plusieurs fois avant de se reposer sur sa valeur finale, et vous ne voudriez pas gaspiller des ressources en tirant immédiatement des observateurs pour chaque changement. .

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

J'espère que cela t'aides. J'ai vraiment dû apprendre un peu pour écrire ce truc, ce qui était un peu le point.

Alexander Wallace Matchneer
la source
3
Grande rédaction! J'entends des rumeurs selon lesquelles la chose "les observateurs tirent instantanément" pourrait changer à un moment donné, pour les rendre retardés comme des liaisons.
Jo Liss
@JoLiss ouais, j'ai l'impression d'en entendre parler depuis quelques mois ... je ne sais pas si / quand ça
arrivera
1
Brendan Briggs a fait une excellente présentation sur la boucle de course lors de la rencontre Ember.js NYC de janvier 2014. Vidéo ici: youtube.com/watch?v=iCZUKFNXA0k
Luke Melia
1
Cette réponse était la meilleure ressource que j'ai trouvée sur Ember Run Loop, très bon travail! J'ai récemment publié un didacticiel complet sur la boucle d'exécution basé sur votre travail qui, je l'espère, décrit encore plus de détails sur ce mécanisme. Disponible ici sur.netguru.co/ember-ebook-form
Kuba Niechciał