Что такое Ember RunLoop и как он работает?


96

Я пытаюсь понять, как работает Ember RunLoop и что заставляет его работать. Я просмотрел документацию , но у меня осталось много вопросов. Мне интересно лучше понять, как работает RunLoop, чтобы я мог выбрать подходящий метод в его пространстве имен, когда мне придется отложить выполнение некоторого кода на более позднее время.

  • Когда запускается Ember RunLoop. Это зависит от маршрутизатора, представлений, контроллеров или чего-то еще?
  • сколько времени это примерно займет (я знаю, что это довольно глупо спрашивать и зависит от многих вещей, но я ищу общую идею, или, может быть, есть минимальное или максимальное время, которое может занять цикл выполнения)
  • Выполняется ли RunLoop постоянно или просто указывает период времени от начала до конца выполнения и может не работать в течение некоторого времени.
  • Если представление создается из одного цикла RunLoop, гарантировано ли, что все его содержимое попадет в DOM к моменту завершения цикла?

Простите меня, если это очень простые вопросы, я думаю, понимание их поможет таким новичкам, как я, лучше использовать Ember.


5
По циклу выполнения не очень много документации. На этой неделе я попытаюсь составить короткую презентацию по нему.
Люк Мелия

2
@LukeMelia, этот вопрос все еще отчаянно требует вашего внимания, и похоже, что некоторые другие люди ищут ту же информацию. Было бы замечательно, если бы у вас была возможность, поделиться своими мыслями о RunLoop.
Aras

Ответы:


199

Обновление 10/9/2013: ознакомьтесь с этой интерактивной визуализацией цикла выполнения: https://machty.s3.amazonaws.com/ember-run-loop-visual/index.html

Обновление от 09.05.2013: все основные концепции, приведенные ниже, все еще актуальны, но на момент этого коммита реализация Ember Run Loop была выделена в отдельную библиотеку backburner.js с некоторыми очень незначительными различиями в API.

Прежде всего прочтите это:

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

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

Они не на 100% точны для Ember, но основные концепции и мотивация RunLoop в целом применимы к Ember; отличаются лишь некоторые детали реализации. Но перейдем к вашим вопросам:

Когда запускается Ember RunLoop. Это зависит от маршрутизатора, представлений, контроллеров или чего-то еще?

Все основные пользовательские события (например, события клавиатуры, события мыши и т.д.) запускают цикл выполнения. Это гарантирует, что любые изменения, внесенные в связанные свойства с помощью захваченного события (мышь / клавиатура / таймер и т. Д.), Будут полностью распространены по всей системе привязки данных Ember перед возвратом управления обратно в систему. Итак, перемещение мыши, нажатие клавиши, нажатие кнопки и т. Д. Запускает цикл выполнения.

сколько времени это примерно займет (я знаю, что это довольно глупо спрашивать и зависит от многих вещей, но я ищу общую идею, или, может быть, есть минимальное или максимальное время, которое может занять цикл выполнения)

RunLoop никогда не будет отслеживать, сколько времени требуется для распространения всех изменений в системе, а затем останавливать RunLoop после достижения максимального времени; скорее, RunLoop всегда будет работать до завершения и не остановится до тех пор, пока не будут вызваны все таймеры с истекшим сроком действия, и, возможно, их привязки не будут распространены, и так далее. Очевидно, что чем больше изменений необходимо распространить из одного события, тем больше времени потребуется для завершения RunLoop. Вот (довольно несправедливый) пример того, как RunLoop может увязнуть с распространением изменений по сравнению с другой структурой (Backbone), у которой нет цикла выполнения: http://jsfiddle.net/jashkenas/CGSd5/. Мораль истории: RunLoop действительно быстр для большинства вещей, которые вы когда-либо хотели делать в Ember, и именно в этом заключается большая часть силы Ember, но если вы обнаружите, что хотите анимировать 30 кругов с помощью Javascript со скоростью 60 кадров в секунду, Возможно, это лучший способ сделать это, чем полагаться на RunLoop от Ember.

Выполняется ли RunLoop постоянно или просто указывает период времени от начала до конца выполнения и может не работать в течение некоторого времени.

Он не выполняется все время - он должен в какой-то момент вернуть управление системе, иначе ваше приложение зависнет - это отличается, скажем, от цикла выполнения на сервере, который имеет while(true)и продолжается до бесконечности. сервер получает сигнал на выключение ... Ember RunLoop не имеет такого, while(true)но запускается только в ответ на события пользователя / таймера.

Если представление создается из одного цикла RunLoop, гарантировано ли, что все его содержимое попадет в DOM к моменту завершения цикла?

Посмотрим, сможем ли мы это выяснить. Одно из больших изменений от SC к Ember RunLoop заключается в том, что вместо циклического перехода между invokeOnceи invokeLast(что вы видите на диаграмме в первой ссылке о RL SproutCore) Ember предоставляет вам список «очередей», которые в В ходе цикла выполнения вы можете запланировать действия (функции, которые будут вызываться во время цикла выполнения), указав, к какой очереди принадлежит действие (пример из источника:) Ember.run.scheduleOnce('render', bindView, 'rerender');.

Если вы посмотрите на run_loop.jsв исходном коде, вы видите Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];, но если вы откроете отладчик JavaScript в браузере в приложении Ember и оценить Ember.run.queues, вы получите более полный список очередей: ["sync", "actions", "render", "afterRender", "destroy", "timers"]. Ember сохраняет свою кодовую базу довольно модульной, и они позволяют вашему коду, а также своему собственному коду в отдельной части библиотеки, вставлять больше очередей. В этом случае библиотека Ember Views вставляет renderи afterRenderставит в очередь, особенно после actionsочереди. Я пойму, почему это может быть через секунду. Во-первых, алгоритм RunLoop:

Алгоритм RunLoop практически такой же, как описано в статьях цикла выполнения SC выше:

  • Вы запускаете свой код между RunLoop, .begin()и .end()только в Ember вы захотите вместо этого запустить свой код внутри Ember.run, который будет внутренне вызывать beginи endдля вас. (Только внутренний код цикла запуска в базе кода Ember все еще использует beginи end, таким образом , вы должны просто придерживаться Ember.run)
  • После end()вызова RunLoop включается, чтобы распространить каждое изменение, сделанное фрагментом кода, переданным Ember.runфункции. Это включает распространение значений связанных свойств, отображение изменений представления в DOM и т. Д. Порядок, в котором выполняются эти действия (привязка, рендеринг элементов DOM и т. Д.), Определяется Ember.run.queuesмассивом, описанным выше:
  • Цикл выполнения начнется с первой очереди, то есть sync. Он выполнит все действия, которые были запланированы syncкодом в очереди Ember.run. Эти действия могут сами по себе планировать выполнение дополнительных действий во время того же цикла выполнения, и цикл выполнения должен обеспечивать выполнение всех действий до тех пор, пока не будут сброшены все очереди. Это делается так: в конце каждой очереди RunLoop просматривает все ранее очищенные очереди и проверяет, были ли запланированы какие-либо новые действия. Если это так, он должен начать с начала самой ранней очереди с невыполненными запланированными действиями и очистить очередь, продолжая отслеживать свои шаги и при необходимости начинать заново, пока все очереди не будут полностью пусты.

В этом суть алгоритма. Вот как связанные данные распространяются через приложение. Вы можете ожидать, что после завершения RunLoop все связанные данные будут полностью распространены. Итак, что насчет элементов DOM?

Здесь важен порядок очередей, в том числе добавленных библиотекой Ember Views. Обратите внимание на это renderи afterRenderпридите после sync, и action. syncОчередь содержит все действия для распространения связанных данных. ( actionпосле этого редко используется в источнике Ember). Основываясь на приведенном выше алгоритме, гарантируется, что к тому времени, когда RunLoop попадет в renderочередь, все привязки данных завершат синхронизацию. Это сделано намеренно: вы бы не хотели выполнять дорогостоящую задачу по отрисовке элементов DOM раньше.синхронизация привязок данных, так как это, вероятно, потребует повторного рендеринга элементов DOM с обновленными данными - очевидно, очень неэффективный и подверженный ошибкам способ очистки всех очередей RunLoop. Таким образом, Ember продуманно выполняет всю возможную работу по связыванию данных перед рендерингом элементов DOM в renderочереди.

Итак, наконец, чтобы ответить на ваш вопрос, да, вы можете ожидать, что все необходимые отрисовки DOM будут выполнены к моменту Ember.runзавершения. Вот jsFiddle для демонстрации: http://jsfiddle.net/machty/6p6XJ/328/

Что еще нужно знать о RunLoop

Наблюдатели против привязок

Важно отметить, что наблюдатели и привязки, обладая аналогичной функциональностью реагирования на изменения в «наблюдаемом» свойстве, ведут себя совершенно по-разному в контексте RunLoop. Распространение syncпривязки , как мы видели, ставится в очередь для выполнения в RunLoop. Наблюдатели, с другой стороны, запускаются немедленно, когда наблюдаемое свойство изменяется, без необходимости предварительного включения в очередь RunLoop. Если Observer и привязка «наблюдают» за одним и тем же свойством, наблюдатель всегда будет вызываться в 100% случаев до обновления привязки.

scheduleOnce и Ember.run.once

Одно из значительных повышений эффективности в автоматически обновляемых шаблонах Ember основано на том факте, что благодаря RunLoop несколько идентичных действий RunLoop могут быть объединены («устранены», если хотите) в одно действие. Если вы посмотрите на run_loop.jsвнутреннее устройство, вы увидите функции, которые способствуют такому поведению, - это связанные функции scheduleOnceи Em.run.once. Разница между ними не так важна, как знание того, что они существуют, и то, как они могут отбрасывать повторяющиеся действия в очереди, чтобы предотвратить много раздутых и расточительных вычислений во время цикла выполнения.

А как насчет таймеров?

Несмотря на то, что «таймеры» являются одной из очередей по умолчанию, перечисленных выше, Ember ссылается только на очередь в своих тестовых примерах RunLoop. Судя по некоторым описаниям из приведенных выше статей о таймерах, которые срабатывают в последнюю очередь, похоже, что такая очередь использовалась бы во времена SproutCore. В Ember timersочередь не используется. Вместо этого RunLoop может быть запущен внутренним управляемым setTimeoutсобытием (см. invokeLaterTimersФункцию), которое достаточно интеллектуально, чтобы перебрать все существующие таймеры, запустить все те, у которых истек срок действия, определить самый ранний будущий таймер и установить внутреннийsetTimeoutтолько для этого события, которое снова запустит RunLoop при срабатывании. Этот подход более эффективен, чем при каждом вызове таймера setTimeout и пробуждении, поскольку в этом случае необходимо выполнить только один вызов setTimeout, а RunLoop достаточно умен, чтобы запускать все разные таймеры, которые могут срабатывать одновременно. время.

Дальнейшая дребезг с syncочередью

Вот фрагмент из цикла выполнения в середине цикла по всем очередям в цикле выполнения. Обратите внимание на особый случай для syncочереди: потому что syncэто особенно изменчивая очередь, в которой данные распространяются во всех направлениях, Ember.beginPropertyChanges()вызывается для предотвращения увольнения каких-либо наблюдателей, за которым следует вызов Ember.endPropertyChanges. Это мудро: если в процессе очистки syncочереди вполне возможно, что свойство объекта изменится несколько раз, прежде чем будет опираться на свое окончательное значение, и вы не захотите тратить ресурсы, немедленно увольняя наблюдателей при каждом отдельном изменении. .

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

Надеюсь это поможет. Мне определенно пришлось немного научиться, чтобы написать эту вещь, что было своего рода сутью.


3
Отличное описание! Я слышу слухи о том, что принцип «наблюдатели стреляют мгновенно» может в какой-то момент измениться, чтобы они задерживались, как привязки.
Jo Liss

@JoLiss да, мне кажется, что я слышал об этом в течение нескольких месяцев ... не уверен, если / когда это
произойдет

1
Брендан Бриггс сделал отличную презентацию о Run Loop на встрече Ember.js в Нью-Йорке в январе 2014 года. Видео здесь: youtube.com/watch?v=iCZUKFNXA0k
Люк Мелия

1
Этот ответ был лучшим ресурсом, который я нашел о Ember Run Loop, очень хорошая работа! Недавно я опубликовал обширное руководство по Run Loop, основанное на вашей работе, которое, надеюсь, описывает еще больше деталей этого механизма. Доступно здесь на.netguru.co
ember-
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.