Cos'è Ember RunLoop e come funziona?


96

Sto cercando di capire come funziona Ember RunLoop e cosa lo fa funzionare. Ho esaminato la documentazione , ma ho ancora molte domande al riguardo. Mi interessa capire meglio come funziona RunLoop in modo da poter scegliere il metodo appropriato all'interno del suo spazio dei nomi, quando devo rimandare l'esecuzione di un codice per un momento successivo.

  • Quando inizia Ember RunLoop. Dipende da Router, Visualizzazioni, Controller o qualcos'altro?
  • quanto tempo ci vuole circa (so che è piuttosto sciocco chiedere e dipende da molte cose ma sto cercando un'idea generale, o forse se c'è un tempo minimo o massimo che un ciclo di esecuzione può impiegare)
  • RunLoop viene eseguito sempre o indica solo un periodo di tempo dall'inizio alla fine dell'esecuzione e potrebbe non essere eseguito per un po 'di tempo.
  • Se una vista viene creata dall'interno di un RunLoop, è garantito che tutto il suo contenuto entrerà nel DOM al termine del ciclo?

Perdonami se queste sono domande molto semplici, penso che comprenderle aiuterà i noob come me a usare meglio Ember.


5
Non ci sono ottimi documenti sul ciclo di esecuzione. Questa settimana proverò a mettere insieme un breve mazzo di diapositive.
Luke Melia

2
@LukeMelia questa domanda ha ancora un disperato bisogno della tua attenzione e sembra che altre persone stiano cercando le stesse informazioni. Sarebbe meraviglioso, se ne avessi la possibilità, condividere le tue intuizioni su RunLoop.
Aras

Risposte:


199

Aggiornamento 10/9/2013: controlla questa visualizzazione interattiva del ciclo di esecuzione: https://machty.s3.amazonaws.com/ember-run-loop-visual/index.html

Aggiornamento 5/9/2013: tutti i concetti di base di seguito sono ancora aggiornati, ma a partire da questo commit , l'implementazione di Ember Run Loop è stata suddivisa in una libreria separata chiamata backburner.js , con alcune differenze API molto minori.

Prima di tutto, leggi questi:

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

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

Non sono accurati al 100% per Ember, ma i concetti fondamentali e la motivazione alla base di RunLoop si applicano ancora generalmente a Ember; solo alcuni dettagli di implementazione differiscono. Ma, passiamo alle tue domande:

Quando inizia Ember RunLoop. Dipende da Router, Visualizzazioni, Controller o qualcos'altro?

Tutti gli eventi utente di base (ad esempio eventi della tastiera, eventi del mouse, ecc.) Attiveranno il ciclo di esecuzione. Ciò garantisce che qualsiasi modifica apportata alle proprietà associate dall'evento acquisito (mouse / tastiera / timer / ecc.) Venga completamente propagata in tutto il sistema di associazione dati di Ember prima di restituire il controllo al sistema. Quindi, muovendo il mouse, premendo un tasto, facendo clic su un pulsante, ecc., Tutto avvia il ciclo di esecuzione.

quanto tempo ci vuole circa (so che è piuttosto sciocco chiedere e dipende da molte cose ma sto cercando un'idea generale, o forse se c'è un tempo minimo o massimo che un ciclo di esecuzione può impiegare)

In nessun momento RunLoop terrà traccia di quanto tempo impiega a propagare tutte le modifiche attraverso il sistema e quindi interromperà RunLoop dopo aver raggiunto un limite di tempo massimo; piuttosto, il RunLoop verrà sempre eseguito fino al completamento e non si fermerà fino a quando tutti i timer scaduti non saranno stati chiamati, i collegamenti propagati e forse i loro collegamenti propagati e così via. Ovviamente, più cambiamenti devono essere propagati da un singolo evento, più tempo impiegherà RunLoop a finire. Ecco un esempio (piuttosto ingiusto) di come RunLoop può impantanarsi con la propagazione delle modifiche rispetto a un altro framework (Backbone) che non ha un ciclo di esecuzione: http://jsfiddle.net/jashkenas/CGSd5/. Morale della storia: RunLoop è davvero veloce per la maggior parte delle cose che vorresti fare in Ember, ed è qui che risiede gran parte del potere di Ember, ma se ti ritrovi a voler animare 30 cerchi con Javascript a 60 fotogrammi al secondo, ecco potrebbero essere modi migliori per farlo piuttosto che affidarsi a RunLoop di Ember.

RunLoop viene eseguito sempre o indica solo un periodo di tempo dall'inizio alla fine dell'esecuzione e potrebbe non essere eseguito per un po 'di tempo.

Non viene eseguito sempre - deve restituire il controllo al sistema a un certo punto altrimenti la tua app si bloccherebbe - è diverso, ad esempio, da un ciclo di esecuzione su un server che ha un while(true)e va avanti all'infinito finché il server riceve un segnale per spegnersi ... Ember RunLoop non ne ha, while(true)ma viene avviato solo in risposta agli eventi utente / timer.

Se una vista viene creata dall'interno di un RunLoop, è garantito che tutto il suo contenuto entrerà nel DOM al termine del ciclo?

Vediamo se riusciamo a capirlo. Uno dei grandi cambiamenti da SC a Ember RunLoop è che, invece di scorrere avanti e indietro tra invokeOncee invokeLast(che vedi nel diagramma nel primo collegamento sull'RL di SproutCore), Ember ti fornisce un elenco di 'code' che, nel nel corso di un ciclo di esecuzione, è possibile programmare azioni (funzioni da chiamare durante il ciclo di esecuzione) specificando a quale coda appartiene l'azione (esempio dall'origine :) Ember.run.scheduleOnce('render', bindView, 'rerender');.

Se si guarda run_loop.jsnel codice sorgente, si vede Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];, ma se si apre il debugger JavaScript nel browser in un app Ember e valutare Ember.run.queues, si ottiene un elenco più completo di code: ["sync", "actions", "render", "afterRender", "destroy", "timers"]. Ember mantiene la loro base di codice piuttosto modulare e consentono al tuo codice, così come al proprio codice in una parte separata della libreria, di inserire più code. In questo caso, la libreria Ember Views inserisce rendere afterRenderaccoda, in particolare dopo la actionscoda. Arriverò al motivo per cui potrebbe essere in un secondo. Innanzitutto, l'algoritmo RunLoop:

L'algoritmo RunLoop è praticamente lo stesso descritto negli articoli del ciclo di esecuzione di SC sopra:

  • Esegui il tuo codice tra RunLoop .begin()e .end(), solo in Ember, vorrai invece eseguire il tuo codice all'interno Ember.run, che chiamerà internamente begine endper te. (Solo il codice del ciclo di esecuzione interno nella base di codice Ember utilizza ancora begine end, quindi dovresti semplicemente attenersi a Ember.run)
  • Dopo end()essere stato chiamato, RunLoop si avvia per propagare ogni singola modifica apportata dal blocco di codice passato alla Ember.runfunzione. Ciò include la propagazione dei valori delle proprietà associate, il rendering delle modifiche della vista al DOM, ecc. Ecc. L'ordine in cui vengono eseguite queste azioni (associazione, rendering di elementi DOM, ecc.) È determinato Ember.run.queuesdall'array descritto sopra:
  • Il ciclo di esecuzione inizierà dalla prima coda, che è sync. Eseguirà tutte le azioni che sono state pianificate nella synccoda dal Ember.runcodice. Queste azioni possono anche programmare più azioni da eseguire durante questo stesso RunLoop, e spetta a RunLoop assicurarsi di eseguire ogni azione fino a quando tutte le code non vengono scaricate. In questo modo, alla fine di ogni coda, RunLoop esaminerà tutte le code precedentemente scaricate e verificherà se sono state pianificate nuove azioni. In tal caso, deve iniziare dall'inizio della prima coda con azioni pianificate non eseguite e svuotare la coda, continuando a tracciare i suoi passaggi e ricominciare quando necessario finché tutte le code non sono completamente vuote.

Questa è l'essenza dell'algoritmo. È così che i dati associati vengono propagati attraverso l'app. Ci si può aspettare che una volta che un RunLoop viene eseguito fino al completamento, tutti i dati associati saranno completamente propagati. Allora, per quanto riguarda gli elementi DOM?

L'ordine delle code, comprese quelle aggiunte dalla libreria Ember Views, è importante qui. Notalo rendere afterRendervieni dopo sync, e action. La synccoda contiene tutte le azioni per la propagazione dei dati associati. ( action, dopo di che, è usato solo scarsamente nella fonte Ember). In base all'algoritmo di cui sopra, è garantito che nel momento in cui RunLoop arriva alla rendercoda, tutte le associazioni di dati avranno terminato la sincronizzazione. Ciò è di progettazione: non si vuole a svolgere il compito di rendere costoso elementi DOM primasincronizzazione dei data-bindings, dal momento che ciò richiederebbe probabilmente il riesame degli elementi DOM con dati aggiornati - ovviamente un modo molto inefficiente e soggetto a errori per svuotare tutte le code di RunLoop. Quindi Ember esplora in modo intelligente tutto il lavoro di associazione dei dati possibile prima di rendere gli elementi DOM nella rendercoda.

Quindi, finalmente, per rispondere alla tua domanda, sì, puoi aspettarti che tutti i rendering DOM necessari avranno avuto luogo entro la fine del tempo Ember.run. Ecco un jsFiddle da dimostrare: http://jsfiddle.net/machty/6p6XJ/328/

Altre cose da sapere su RunLoop

Osservatori vs binding

È importante notare che Observers e Bindings, pur avendo la funzionalità simile di rispondere ai cambiamenti in una proprietà "controllata", si comportano in modo totalmente diverso nel contesto di un RunLoop. La propagazione del binding, come abbiamo visto, viene pianificata nella synccoda per essere eventualmente eseguita da RunLoop. Gli osservatori, d'altra parte, si attivano immediatamente quando la proprietà osservata cambia senza dover prima essere programmati in una coda RunLoop. Se un Observer e un binding "osservano" tutti la stessa proprietà, l'osservatore verrà sempre chiamato il 100% delle volte prima che il binding venga aggiornato.

scheduleOnce e Ember.run.once

Uno dei grandi aumenti di efficienza nei modelli di aggiornamento automatico di Ember si basa sul fatto che, grazie a RunLoop, più azioni RunLoop identiche possono essere unite ("rimbalzate", se vuoi) in un'unica azione. Se guardi negli run_loop.jsinterni, vedrai che le funzioni che facilitano questo comportamento sono le funzioni correlate scheduleOncee Em.run.once. La differenza tra loro non è così importante quanto sapere che esistono e come possono scartare le azioni duplicate in coda per evitare calcoli eccessivi e dispendiosi durante il ciclo di esecuzione.

E i timer?

Anche se "timer" è una delle code predefinite elencate sopra, Ember fa riferimento alla coda solo nei casi di test RunLoop. Sembra che una tale coda sarebbe stata utilizzata nei giorni di SproutCore sulla base di alcune delle descrizioni degli articoli sopra sui timer come l'ultima cosa da sparare. In Ember, la timerscoda non viene utilizzata. Invece, il RunLoop può essere attivato da un setTimeoutevento gestito internamente (vedere la invokeLaterTimersfunzione), che è abbastanza intelligente da scorrere tutti i timer esistenti, attivare tutti quelli che sono scaduti, determinare il primo timer futuro e impostare un timer internosetTimeoutsolo per quell'evento, che farà girare di nuovo RunLoop quando si attiva. Questo approccio è più efficiente che avere ogni chiamata con timer setTimeout e svegliarsi, poiché in questo caso è necessario effettuare una sola chiamata setTimeout e RunLoop è abbastanza intelligente da attivare tutti i diversi timer che potrebbero attivarsi contemporaneamente tempo.

Ulteriore rimbalzo con la synccoda

Ecco un frammento del ciclo di esecuzione, nel mezzo di un ciclo attraverso tutte le code del ciclo di esecuzione. Si noti il ​​caso speciale della synccoda: poiché syncè una coda particolarmente volatile, in cui i dati vengono propagati in ogni direzione, Ember.beginPropertyChanges()viene chiamata per evitare che eventuali osservatori vengano attivati, seguita da una chiamata a Ember.endPropertyChanges. Questo è saggio: se durante lo svuotamento della synccoda, è del tutto possibile che una proprietà su un oggetto cambi più volte prima di riposare sul suo valore finale, e non vorrai sprecare risorse sparando immediatamente osservatori per ogni singola modifica .

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

Spero che questo ti aiuti. Sicuramente ho dovuto imparare un bel po 'solo per scrivere questa cosa, il che era il punto.


3
Ottimo commento! Ho sentito voci secondo cui la cosa "gli osservatori sparano all'istante" potrebbe cambiare a un certo punto, per renderli ritardati come legature.
Jo Liss

@ JoLiss sì, mi sento come se ne sentissi parlare per alcuni mesi ... non sono sicuro se / quando ce la farà.
Alexander Wallace Matchneer

1
Brendan Briggs ha fatto un'ottima presentazione sul Run Loop al meetup di New York Ember.js di gennaio 2014. Video qui: youtube.com/watch?v=iCZUKFNXA0k
Luke Melia

1
Questa risposta è stata la migliore risorsa che ho trovato su Ember Run Loop, ottimo lavoro! Di recente ho pubblicato un ampio tutorial sul Run Loop basato sul tuo lavoro che spero descriva ancora più dettagli di quel meccanismo. Disponibile qui su.netguru.co/ember-ebook-form
Kuba Niechciał
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.