O que é Ember RunLoop e como funciona?

96

Estou tentando entender como o Ember RunLoop funciona e o que o faz funcionar. Eu olhei a documentação , mas ainda tenho muitas dúvidas sobre ela. Estou interessado em entender melhor como funciona o RunLoop para poder escolher o método apropriado dentro de seu espaço de nomes, quando tiver que adiar a execução de algum código para um momento posterior.

  • Quando o Ember RunLoop é iniciado. Depende do roteador, visualizações ou controladores ou de outra coisa?
  • quanto tempo leva aproximadamente (eu sei que é bastante bobo perguntar e depende de muitas coisas, mas estou procurando uma ideia geral, ou talvez se há um tempo mínimo ou máximo que um loop de execução pode levar)
  • O RunLoop está sendo executado o tempo todo ou apenas indica um período de tempo do início ao fim da execução e pode não ser executado por algum tempo?
  • Se uma visualização for criada a partir de um RunLoop, é garantido que todo o seu conteúdo chegará ao DOM no momento em que o loop terminar?

Perdoe-me se essas são perguntas muito básicas, acho que entendê-las ajudará noobs como eu a usar melhor o Ember.

Aras
fonte
5
Não existem bons documentos sobre o loop de execução. Vou tentar montar um pequeno conjunto de slides sobre ele esta semana.
Luke Melia
2
@LukeMelia esta questão ainda precisa desesperadamente de sua atenção e parece que outras pessoas estão procurando pelas mesmas informações. Seria maravilhoso, se você tivesse a chance, de compartilhar seus insights sobre RunLoop.
Aras

Respostas:

199

Atualização 09/10/2013: Confira esta visualização interativa do loop de execução: https://machty.s3.amazonaws.com/ember-run-loop-visual/index.html

Atualização 5/9/2013: todos os conceitos básicos abaixo ainda estão atualizados, mas a partir deste commit , a implementação do Ember Run Loop foi dividida em uma biblioteca separada chamada backburner.js , com algumas diferenças de API muito pequenas.

Primeiro, leia estes:

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

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

Eles não são 100% precisos para o Ember, mas os principais conceitos e motivação por trás do RunLoop ainda se aplicam ao Ember; apenas alguns detalhes de implementação diferem. Mas, para suas perguntas:

Quando o Ember RunLoop é iniciado. Depende do roteador, visualizações ou controladores ou de outra coisa?

Todos os eventos básicos do usuário (por exemplo, eventos de teclado, eventos de mouse, etc.) iniciarão o loop de execução. Isso garante que todas as alterações feitas nas propriedades associadas pelo evento capturado (mouse / teclado / cronômetro / etc) sejam totalmente propagadas por todo o sistema de vinculação de dados do Ember antes de retornar o controle de volta ao sistema. Portanto, mover o mouse, pressionar uma tecla, clicar em um botão, etc., todos iniciam o loop de execução.

quanto tempo leva aproximadamente (eu sei que é bastante bobo perguntar e depende de muitas coisas, mas estou procurando uma ideia geral, ou talvez se há um tempo mínimo ou máximo que um loop de execução pode levar)

Em nenhum momento o RunLoop controlará quanto tempo está levando para propagar todas as alterações pelo sistema e, em seguida, interromperá o RunLoop após atingir um limite de tempo máximo; em vez disso, o RunLoop sempre será executado até a conclusão e não parará até que todos os timers expirados tenham sido chamados, as ligações propagadas e talvez suas ligações propagadas e assim por diante. Obviamente, quanto mais mudanças precisarem ser propagadas de um único evento, mais tempo o RunLoop levará para ser concluído. Aqui está um exemplo (bastante injusto) de como o RunLoop pode ficar atolado com a propagação de alterações em comparação com outro framework (Backbone) que não tem um loop de execução: http://jsfiddle.net/jashkenas/CGSd5/. Moral da história: o RunLoop é realmente rápido para a maioria das coisas que você gostaria de fazer no Ember, e é onde reside grande parte do poder do Ember, mas se você quiser animar 30 círculos com Javascript a 60 quadros por segundo, aí podem ser maneiras melhores de fazer isso do que confiar no RunLoop de Ember.

O RunLoop está sendo executado o tempo todo ou apenas indica um período de tempo do início ao fim da execução e pode não ser executado por algum tempo?

Ele não é executado o tempo todo - ele precisa retornar o controle ao sistema em algum ponto ou então seu aplicativo travaria - é diferente de, digamos, um loop de execução em um servidor que tem um while(true)e continua por infinito até o servidor recebe um sinal para desligar ... o Ember RunLoop não tem, while(true)mas só é ligado em resposta a eventos de usuário / temporizador.

Se uma visualização for criada a partir de um RunLoop, é garantido que todo o seu conteúdo chegará ao DOM no momento em que o loop terminar?

Vamos ver se podemos descobrir isso. Uma das grandes mudanças de SC para Ember RunLoop é que, em vez de alternar entre invokeOncee invokeLast(que você vê no diagrama no primeiro link sobre RL de SproutCore), Ember fornece uma lista de 'filas' que, no durante um loop de execução, você pode agendar ações (funções a serem chamadas durante o loop de execução) especificando a qual fila a ação pertence (exemplo da fonte Ember.run.scheduleOnce('render', bindView, 'rerender');:).

Se você olhar run_loop.jsno código fonte, você vê Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];, mas se você abrir o seu depurador de JavaScript no navegador em um aplicativo Ember e avaliar Ember.run.queues, você obter uma lista mais completa de filas: ["sync", "actions", "render", "afterRender", "destroy", "timers"]. O Ember mantém sua base de código bastante modular e possibilita que seu código, assim como seu próprio código em uma parte separada da biblioteca, insira mais filas. Nesse caso, a biblioteca Ember Views insere rendere afterRenderenfileira, especificamente após a actionsfila. Vou entender por que isso pode ser em um segundo. Primeiro, o algoritmo RunLoop:

O algoritmo RunLoop é praticamente igual ao descrito nos artigos de loop de execução SC acima:

  • Você executa seu código entre RunLoop .begin()e .end(), apenas no Ember, você deseja executar seu código dentro Ember.run, que chamará internamente begine endpara você. (Apenas o código de loop de execução interno na base de código Ember ainda usa begine end, então você deve ficar com Ember.run)
  • Depois de end()ser chamado, o RunLoop entra em ação para propagar cada mudança feita pelo pedaço de código passado para a Ember.runfunção. Isso inclui a propagação de valores de propriedades vinculadas, renderização de alterações de visualização no DOM, etc. etc. A ordem em que essas ações (vinculação, renderização de elementos DOM etc.) são realizadas é determinada pela Ember.run.queuesmatriz descrita acima:
  • O loop de execução começará na primeira fila, que é sync. Ele executará todas as ações que foram programadas na syncfila pelo Ember.runcódigo. Essas ações também podem programar mais ações a serem realizadas durante o mesmo RunLoop, e cabe ao RunLoop garantir que execute todas as ações até que todas as filas sejam liberadas. A maneira como ele faz isso é, no final de cada fila, o RunLoop examinará todas as filas previamente liberadas e verá se alguma nova ação foi agendada. Em caso afirmativo, ele deve começar no início da primeira fila com ações programadas não realizadas e limpar a fila, continuando a rastrear suas etapas e reiniciar quando necessário até que todas as filas estejam completamente vazias.

Essa é a essência do algoritmo. É assim que os dados vinculados são propagados pelo aplicativo. Você pode esperar que, assim que um RunLoop for executado até a conclusão, todos os dados vinculados serão totalmente propagados. Então, e quanto aos elementos DOM?

A ordem das filas, incluindo aquelas adicionadas pela biblioteca Ember Views, é importante aqui. Observe isso rendere afterRendervenha depois sync, e action. A syncfila contém todas as ações para propagação de dados vinculados. ( actiondepois disso, é usado apenas esparsamente na fonte Ember). Com base no algoritmo acima, é garantido que, no momento em que o RunLoop chega à renderfila, todas as ligações de dados terão concluído a sincronização. Isso ocorre por design: você não gostaria de realizar a tarefa cara de renderizar elementos DOM antessincronizar as vinculações de dados, já que isso provavelmente exigiria uma nova renderização dos elementos DOM com dados atualizados - obviamente, uma maneira muito ineficiente e sujeita a erros de esvaziar todas as filas RunLoop. Portanto, o Ember explora de forma inteligente todo o trabalho de vinculação de dados que pode antes de renderizar os elementos DOM na renderfila.

Então, finalmente, para responder à sua pergunta, sim, você pode esperar que todas as renderizações DOM necessárias tenham ocorrido quando o tempo Ember.runterminar. Aqui está um jsFiddle para demonstrar: http://jsfiddle.net/machty/6p6XJ/328/

Outras coisas para saber sobre o RunLoop

Observadores vs. Ligações

É importante observar que Observers e Bindings, embora tenham a funcionalidade semelhante de responder às mudanças em uma propriedade "observada", se comportam de maneira totalmente diferente no contexto de um RunLoop. A propagação de vinculação, como vimos, é agendada na syncfila para ser executada pelo RunLoop. Os observadores, por outro lado, são acionados imediatamente quando a propriedade observada é alterada, sem precisar ser programado primeiro em uma fila RunLoop. Se um Observer e um binding "observarem" a mesma propriedade, o observador sempre será chamado 100% do tempo antes que o binding seja atualizado.

scheduleOnce e Ember.run.once

Um dos grandes aumentos de eficiência nos modelos de atualização automática do Ember se baseia no fato de que, graças ao RunLoop, várias ações RunLoop idênticas podem ser reunidas ("depuradas", se preferir) em uma única ação. Se você olhar para os run_loop.jsinternos, verá que as funções que facilitam esse comportamento são as funções relacionadas scheduleOncee Em.run.once. A diferença entre eles não é tão importante quanto saber que existem e como podem descartar ações duplicadas na fila para evitar muitos cálculos inchados e inúteis durante o loop de execução.

E quanto aos temporizadores?

Mesmo que 'temporizadores' seja uma das filas padrão listadas acima, o Ember só faz referência à fila em seus casos de teste RunLoop. Parece que tal fila teria sido usada nos dias do SproutCore com base em algumas das descrições dos artigos acima sobre temporizadores sendo a última coisa a disparar. Em Ember, a timersfila não é utilizado. Em vez disso, o RunLoop pode ser ativado por um setTimeoutevento gerenciado internamente (consulte a invokeLaterTimersfunção), que é inteligente o suficiente para percorrer todos os temporizadores existentes, disparar todos aqueles que expiraram, determinar o temporizador futuro mais antigo e definir um temporizador internosetTimeoutapenas para aquele evento, o que ativará o RunLoop novamente quando for disparado. Essa abordagem é mais eficiente do que ter cada chamada de timer setTimeout e despertar, uma vez que, neste caso, apenas uma chamada setTimeout precisa ser feita, e o RunLoop é inteligente o suficiente para disparar todos os diferentes timers que podem estar disparando ao mesmo Tempo.

Mais debouncing com a syncfila

Aqui está um fragmento do loop de execução, no meio de um loop por todas as filas do loop de execução. Observe o caso especial da syncfila: por syncser uma fila particularmente volátil, na qual os dados estão sendo propagados em todas as direções, Ember.beginPropertyChanges()é chamada para evitar que qualquer observador seja disparado, seguido por uma chamada para Ember.endPropertyChanges. Isto é sábio: se durante a liberação da syncfila, é inteiramente possível que uma propriedade em um objeto mude várias vezes antes de descansar em seu valor final, e você não gostaria de desperdiçar recursos disparando observadores imediatamente a cada mudança .

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 isto ajude. Eu definitivamente tive que aprender um pouco apenas para escrever isso, o que era mais ou menos o ponto.

Alexander Wallace Matchneer
fonte
3
Excelente redação! Eu ouço rumores de que a coisa "observadores disparam instantaneamente" pode mudar em algum ponto, para atrasá-los como ligações.
Jo Liss
@JoLiss sim, eu sinto que já ouvi sobre isso por alguns meses ... não tenho certeza se / quando vai chegar.
Alexander Wallace Matchneer
1
Brendan Briggs fez uma ótima apresentação sobre o Run Loop no encontro Ember.js NYC de janeiro de 2014. Vídeo aqui: youtube.com/watch?v=iCZUKFNXA0k
Luke Melia
1
Esta resposta foi o melhor recurso que encontrei sobre o Ember Run Loop, trabalho muito bom! Recentemente, publiquei um extenso tutorial sobre o Run Loop com base em seu trabalho que, espero, descreva ainda mais detalhes desse mecanismo. Disponível aqui em.netguru.co/ember-ebook-form
Kuba Niechciał