Co to jest Ember RunLoop i jak działa?

96

Próbuję zrozumieć, jak działa Ember RunLoop i co sprawia, że ​​działa. Przejrzałem dokumentację , ale wciąż mam wiele pytań na jej temat. Jestem zainteresowany lepszym zrozumieniem, jak działa RunLoop, aby móc wybrać odpowiednią metodę w jego przestrzeni nazw, gdy muszę odłożyć wykonanie kodu na później.

  • Kiedy uruchamia się Ember RunLoop. Czy zależy to od routera, widoków, kontrolerów czy czegoś innego?
  • jak długo to w przybliżeniu trwa (wiem, że jest to raczej głupie pytanie i zależne od wielu rzeczy, ale szukam ogólnego pomysłu, a może może jest minimalny lub maksymalny czas, jaki może zająć runloop)
  • Czy RunLoop jest wykonywany przez cały czas, czy tylko wskazuje okres od początku do końca wykonywania i może nie działać przez jakiś czas.
  • Jeśli widok jest tworzony z poziomu jednego RunLoop, czy gwarantuje się, że cała jego zawartość trafi do DOM przed zakończeniem pętli?

Wybacz mi, jeśli są to bardzo podstawowe pytania, myślę, że zrozumienie ich pomoże takim noobom jak ja lepiej używać Ember.

Aras
źródło
5
Nie ma dobrych dokumentów na temat pętli uruchamiania. W tym tygodniu spróbuję ułożyć na nim krótki pokaz slajdów.
Luke Melia
2
@LukeMelia to pytanie wciąż potrzebuje Twojej uwagi i wygląda na to, że inne osoby szukają tych samych informacji. Byłoby wspaniale, gdybyś miał okazję podzielić się swoimi spostrzeżeniami na temat RunLoop.
Aras,

Odpowiedzi:

199

Aktualizacja 09.10.2013: Sprawdź tę interaktywną wizualizację pętli uruchamiania: https://machty.s3.amazonaws.com/ember-run-loop-visual/index.html

Aktualizacja 9.05.2013: wszystkie podstawowe koncepcje poniżej są nadal aktualne, ale od tego zatwierdzenia implementacja Ember Run Loop została podzielona na oddzielną bibliotekę o nazwie backburner.js , z pewnymi bardzo niewielkimi różnicami w API.

Po pierwsze, przeczytaj te:

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

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

Nie są w 100% dokładne w stosunku do Ember, ale podstawowe koncepcje i motywacja RunLoop nadal generalnie odnoszą się do Ember; różnią się tylko niektóre szczegóły implementacji. Ale jeśli chodzi o twoje pytania:

Kiedy uruchamia się Ember RunLoop. Czy zależy to od routera, widoków, kontrolerów czy czegoś innego?

Wszystkie podstawowe zdarzenia użytkownika (np. Zdarzenia klawiatury, zdarzenia myszy itp.) Uruchomią pętlę uruchamiania. Gwarantuje to, że wszelkie zmiany wprowadzone w powiązanych właściwościach przez przechwycone zdarzenie (mysz / klawiatura / zegar / itp.) Są w pełni propagowane w systemie powiązań danych Ember przed zwróceniem sterowania z powrotem do systemu. Tak więc poruszanie myszą, naciśnięcie klawisza, kliknięcie przycisku itp. Uruchamia pętlę uruchamiania.

jak długo to w przybliżeniu trwa (wiem, że jest to raczej głupie pytanie i zależne od wielu rzeczy, ale szukam ogólnego pomysłu, a może może jest minimalny lub maksymalny czas, jaki może zająć runloop)

W żadnym momencie RunLoop nigdy nie będzie śledzić, ile czasu zajmuje propagowanie wszystkich zmian w systemie, a następnie zatrzyma RunLoop po osiągnięciu maksymalnego limitu czasu; raczej RunLoop będzie zawsze działał do końca i nie zatrzyma się, dopóki nie zostaną wywołane wszystkie wygasłe timery, rozpropagowane powiązania i być może ich powiązania, i tak dalej. Oczywiście im więcej zmian trzeba propagować z jednego zdarzenia, tym dłużej RunLoop potrwa do zakończenia. Oto (dość niesprawiedliwy) przykład tego, jak RunLoop może ugrzęznąć w propagowaniu zmian w porównaniu z innym frameworkiem (Backbone), który nie ma pętli uruchamiania: http://jsfiddle.net/jashkenas/CGSd5/. Morał z tej historii: RunLoop jest naprawdę szybki dla większości rzeczy, które kiedykolwiek chciałbyś robić w Ember, i tam leży duża moc Embera, ale jeśli chcesz animować 30 kręgów za pomocą JavaScript przy 60 klatkach na sekundę, tam może być lepszym sposobem rozwiązania tego problemu niż poleganie na RunLoop firmy Ember.

Czy RunLoop jest wykonywany przez cały czas, czy tylko wskazuje okres od początku do końca wykonywania i może nie działać przez jakiś czas.

Nie jest wykonywany przez cały czas - musi w pewnym momencie zwrócić kontrolę z powrotem do systemu, w przeciwnym razie aplikacja zawiesiłaby się - różni się od, powiedzmy, pętli uruchamiania na serwerze, który ma while(true)i działa w nieskończoność do serwer otrzymuje sygnał do wyłączenia ... Ember RunLoop nie ma takiego, while(true)ale jest uruchamiany tylko w odpowiedzi na zdarzenia użytkownika / timera.

Jeśli widok jest tworzony z poziomu jednego RunLoop, czy gwarantuje się, że cała jego zawartość trafi do DOM przed zakończeniem pętli?

Zobaczmy, czy uda nam się to rozgryźć. Jedną z dużych zmian z SC do Ember RunLoop jest to, że zamiast zapętlania się w tę iz powrotem pomiędzy invokeOncei invokeLast(co widać na schemacie w pierwszym linku dotyczącym RL SproutCore), Ember zapewnia listę `` kolejek '', które w przebieg pętli uruchamiania można zaplanować akcje (funkcje, które mają być wywoływane podczas pętli uruchamiania), określając, do której kolejki należy akcja (przykład ze źródła:) Ember.run.scheduleOnce('render', bindView, 'rerender');.

Jeśli spojrzeć run_loop.jsw kodzie źródłowym, widzisz Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];, jednak po otwarciu debugger JavaScript w przeglądarce w aplikacji Ember i ocenić Ember.run.queues, masz pełniejszą listę kolejek: ["sync", "actions", "render", "afterRender", "destroy", "timers"]. Ember utrzymuje swoją bazę kodów dość modularną i umożliwia wstawianie większej liczby kolejek przez Twój kod, a także jego własny kod w oddzielnej części biblioteki. W tym przypadku biblioteka Ember Views wstawia renderi afterRenderkolejkuje, szczególnie po actionskolejce. Za chwilę wyjaśnię, dlaczego może to nastąpić. Najpierw algorytm RunLoop:

Algorytm RunLoop jest prawie taki sam, jak opisano w artykułach o pętli uruchamiania SC powyżej:

  • Uruchamiasz swój kod między RunLoop .begin()i .end()tylko w Ember będziesz chciał zamiast tego uruchomić swój kod w ramach Ember.run, który będzie wewnętrznie wywoływał begini enddla Ciebie. (Tylko wewnętrzny kod pętli uruchamiania w bazie kodu Ember nadal używa begini end, więc powinieneś po prostu trzymać się Ember.run)
  • Po end()wywołaniu RunLoop uruchamia się, aby propagować każdą zmianę wprowadzoną przez fragment kodu przekazany do Ember.runfunkcji. Obejmuje to propagowanie wartości powiązanych właściwości, renderowanie zmian widoku w modelu DOM itp. Kolejność wykonywania tych czynności (wiązanie, renderowanie elementów DOM itp.) Jest określana przez Ember.run.queuestablicę opisaną powyżej:
  • Pętla uruchamiania rozpocznie się w pierwszej kolejce, czyli sync. Uruchomi wszystkie akcje, które zostały zaplanowane w synckolejce przez Ember.runkod. Te akcje mogą również same zaplanować więcej akcji do wykonania podczas tego samego RunLoop, a RunLoop musi upewnić się, że wykona każdą akcję, dopóki wszystkie kolejki nie zostaną opróżnione. Sposób, w jaki to robi, polega na tym, że na końcu każdej kolejki RunLoop przejrzy wszystkie poprzednio opróżnione kolejki i sprawdzi, czy zostały zaplanowane jakieś nowe akcje. Jeśli tak, musi rozpocząć się na początku najwcześniejszej kolejki z niewykonanymi zaplanowanymi działaniami i opróżnić kolejkę, kontynuując śledzenie jej kroków i rozpoczynając od nowa, gdy jest to konieczne, aż wszystkie kolejki zostaną całkowicie opróżnione.

To jest istota algorytmu. W ten sposób powiązane dane są propagowane przez aplikację. Możesz spodziewać się, że po zakończeniu RunLoop wszystkie powiązane dane zostaną w pełni propagowane. A co z elementami DOM?

Ważna jest tutaj kolejność kolejek, w tym dodanych przez bibliotekę Ember Views. Zauważ to renderi afterRenderprzyjdź później sync, i action. syncKolejka zawiera wszystkie działania na rzecz propagowania dane związane. ( actionpo tym jest tylko rzadko używany w źródle Ember). Opierając się na powyższym algorytmie, gwarantuje się, że zanim RunLoop dotrze do renderkolejki, wszystkie powiązania danych zostaną zsynchronizowane. Jest to zgodne z projektem: nie chcesz, aby wykonać zadanie z drogiego renderowania elementów DOM przedsynchronizowanie powiązań danych, ponieważ prawdopodobnie wymagałoby to ponownego renderowania elementów DOM ze zaktualizowanymi danymi - oczywiście bardzo nieefektywny i podatny na błędy sposób opróżniania wszystkich kolejek RunLoop. Dzięki temu Ember w inteligentny sposób przegląda wszystkie możliwe prace związane z wiązaniem danych przed renderowaniem elementów DOM w renderkolejce.

A więc, na koniec, odpowiadając na twoje pytanie, tak, możesz spodziewać się, że wszelkie niezbędne renderingi DOM zostaną wykonane przed upływem czasu Ember.run. Oto jsFiddle do zademonstrowania: http://jsfiddle.net/machty/6p6XJ/328/

Inne rzeczy, które warto wiedzieć o RunLoop

Obserwatorzy a wiązania

Należy zauważyć, że obserwatorzy i powiązania, mając podobną funkcjonalność reagowania na zmiany we właściwości „obserwowanej”, zachowują się zupełnie inaczej w kontekście RunLoop. Propagacja syncpowiązań , jak widzieliśmy, zostaje zaplanowana w kolejce, aby ostatecznie zostać wykonana przez RunLoop. Z drugiej strony obserwatorzy uruchamiają natychmiast po zmianie obserwowanej właściwości bez konieczności wcześniejszego planowania jej w kolejce RunLoop. Jeśli obserwator i powiązanie „obserwują” tę samą właściwość, obserwator będzie zawsze wywoływany przez 100% czasu wcześniej niż powiązanie zostanie zaktualizowane.

scheduleOnce i Ember.run.once

Jeden z dużych wzrostów wydajności w szablonach automatycznej aktualizacji Ember jest oparty na fakcie, że dzięki RunLoop, wiele identycznych akcji RunLoop można połączyć („zdemontować”, jeśli wolisz) w jedną akcję. Jeśli spojrzysz na run_loop.jswewnętrzne elementy, zobaczysz funkcje, które ułatwiają to zachowanie, są powiązane funkcje scheduleOncei Em.run.once. Różnica między nimi nie jest tak ważna, jak wiedza o ich istnieniu i sposób, w jaki mogą odrzucić zduplikowane akcje w kolejce, aby zapobiec wielu rozdętym, marnotrawnym obliczeniom podczas pętli uruchamiania.

A co z licznikami czasu?

Mimo że „timery” są jedną z domyślnych kolejek wymienionych powyżej, Ember odwołuje się do kolejki tylko w swoich przypadkach testowych RunLoop. Wydaje się, że taka kolejka byłaby używana w czasach SproutCore na podstawie niektórych opisów z powyższych artykułów dotyczących timerów jako ostatniej odpalanej rzeczy. W Ember timerskolejka nie jest używana. Zamiast tego RunLoop może zostać uruchomiony przez setTimeoutzdarzenie zarządzane wewnętrznie (patrz invokeLaterTimersfunkcja), które jest wystarczająco inteligentne, aby przejść przez wszystkie istniejące timery, uruchomić wszystkie wygasłe, określić najwcześniejszy przyszły timer i ustawić wewnętrznysetTimeouttylko dla tego zdarzenia, co spowoduje ponowne uruchomienie RunLoop po uruchomieniu. Takie podejście jest bardziej wydajne niż ustawianie każdego wywołania timera setTimeout i wybudzanie się, ponieważ w tym przypadku wystarczy wykonać tylko jedno wywołanie setTimeout, a RunLoop jest wystarczająco inteligentny, aby uruchomić wszystkie różne timery, które mogą zadziałać w tym samym czasie czas.

Dalsze odbijanie się od synckolejki

Oto fragment z pętli uruchamiania, w środku pętli przez wszystkie kolejki w pętli uruchamiania. Zwróć uwagę na specjalny przypadek synckolejki: ponieważ syncjest to szczególnie niestabilna kolejka, w której dane są propagowane we wszystkich kierunkach, Ember.beginPropertyChanges()jest wywoływana, aby zapobiec wystrzeleniu jakichkolwiek obserwatorów, po których następuje wywołanie Ember.endPropertyChanges. Jest to mądre: jeśli w trakcie opróżniania synckolejki jest całkowicie możliwe, że właściwość obiektu zmieni się wielokrotnie, zanim spocznie na jego ostatecznej wartości, a nie chciałbyś marnować zasobów, natychmiast zwalniając obserwatorów przy każdej pojedynczej zmianie .

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

Mam nadzieję że to pomoże. Zdecydowanie musiałem się sporo nauczyć, żeby to napisać, co było w pewnym sensie.

Alexander Wallace Matchneer
źródło
3
Świetny napis! Słyszę plotki, że „obserwatorzy natychmiast strzelają” może się w pewnym momencie zmienić, powodując ich opóźnienie jak wiązania.
Jo Liss
@JoLiss yeah, wydaje mi się, że słyszałem o tym od kilku miesięcy ... nie jestem pewien, czy / kiedy to
dotrze
1
Brendan Briggs wygłosił świetną prezentację na temat Run Loop na spotkaniu Ember.js NYC w styczniu 2014 roku. Film tutaj: youtube.com/watch?v=iCZUKFNXA0k
Luke Melia,
1
Ta odpowiedź była najlepszym źródłem informacji o Ember Run Loop, bardzo dobra robota! Niedawno opublikowałem obszerny samouczek na temat Run Loop oparty na twoich pracach, który, mam nadzieję, opisuje jeszcze więcej szczegółów tego mechanizmu. Dostępne tutaj na.netguru.co/ember-ebook-form
Kuba Niechciał