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

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

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

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

Ответ 1

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

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

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

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

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

Посмотрим, сможем ли мы это понять. Одно из больших изменений от SC до Ember RunLoop заключается в том, что вместо циклического перехода между invokeOnce и invokeLast (который вы видите на диаграмме в первой ссылке SproutCore RL), 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, чтобы убедиться, что он выполняет каждое действие до тех пор, пока все очереди не будут сброшены. Как это делается, в конце каждой очереди 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

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

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

scheduleOnce и Ember.run.once

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

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

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

Дальнейшее debouncing с очередью 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);
}

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