Опубликован: 10.12.2007 | Доступ: свободный | Студентов: 822 / 20 | Оценка: 5.00 / 5.00 | Длительность: 58:33:00
Лекция 6:

События

6.1. Как Mozilla обрабатывает события

На рисунке 6.1 показано схематическое представление управляемых событиями систем внутри Mozilla.

Все эти подходы мы рассмотрим хотя бы кратко; многие - подробно, но команды будут обсуждаться отдельно, в "Команды" , "Команды", а обработка данных - одна из тем "Объекты XPCOM" , "Объекты XPCOM".

Обзор обработки событий в Mozilla. (Фаза перехвата, Целевой объект, Фаза всплытия, События DOM, Команды, Поток данных, События данных, События таймера, наблюдатель, подписчик, источник событий, Однонаправленное наблюдение, Двунаправленное наблюдение).

Рис. 6.1. Обзор обработки событий в Mozilla. (Фаза перехвата, Целевой объект, Фаза всплытия, События DOM, Команды, Поток данных, События данных, События таймера, наблюдатель, подписчик, источник событий, Однонаправленное наблюдение, Двунаправленное наблюдение).
6.1.1. Планирование ввода и вывода

Всем программам нужна какая-либо форма ввода и вывода, и платформа Mozilla - не исключение. Она может получать данные от устройств, управляемых человеком, используя подключение к сети и Internet, от операционной системы, от других приложений и от самой себя. Она может также отправлять данные в любое из перечисленных мест. Все вместе образует сложную для управления систему возможностей. Что если данные появляются сразу из нескольких мест? Решение, реализованное в Mozilla, использует простую систему планирования, управляемую событиями.

Проблема систем планирования заключается в том, что их прямую поддержку обеспечивает лишь небольшая часть языков программирования, и простые программы эти системы используют довольно редко. Некоторые программисты никогда не сталкиваются с такими системами. Здесь предложено введение в подобные системы для новичков. Более опытные программисты могут обратить внимание, что эта система эквивалентна многопоточной среде и вызову ядра select().

Программа hello, world, - наверное, первая программа, которую все пытаются написать, но она использует только вывод данных. Следующая за ней программа, вероятно, должна и выводить данные, и вводить их. В листинге 6.1 приведена такая программа, написанная с использованием синтаксиса JavaScript. Запустить эту программу в браузере не удастся: это чисто символический пример.

var info; while (true) { 	read info; 	print info; }
Листинг 6.1. Пример первой программы с вводом и выводом

В соответствии с правилами языков третьего поколения, в этой программе есть один блок инструкций, состоящий из двух инструкций, которые постоянно выполняются одна за другой. Эта программа никогда не завершается; она просто выводит все то, что в нее вводят. Она могла быть более структурированной, как показано в листинге 6.2.

var info;
function read_data() {
 read info;
 }
function print_data() {
  print info;
 }
function run() {
  while (true) {
    read_data();
    print_data();
  }
}
run();
Листинг 6.2. Пример первой структурированной программы с вводом и выводом

Хотя вторая программа выполняет только одну инструкцию - run() - очевидно, что это точно такой же пошаговый подход, что и в листинге 6.1. Программы, написанные с использованием систем планирования, работают совсем не так. Та же программа, оформленная как система планирования, могла бы выглядеть так, как в листинге 6.3.

var info;
function read_data() {
  if (!info)
    read info;
}
function print_data() {
  if (info) {
    print info;
    info = null;
  }
}
schedule(read_data, 500);
schedule(print_data, 1000); 
run();
Листинг 6.3. Пример первой программы с вводом и выводом на основе системы планирования

Функция schedule() в этом примере принимает два аргумента: функцию для вызова и временную задержку в миллисекундах. schedule() говорит о том, что указанная функция должна регулярно запускаться по прохождении заданного промежутка времени. run() дает системе команду искать то, что можно запустить. По прохождении половины секунды (500 миллисекунд) будет вызвана функция read_data(). По прохождении секунды (1000 миллисекунд или 2*500 миллисекунд) будут вызваны обе функции. По прохождении полутора секунд (3*500 секунд) read_data() будет вызвана в третий раз и так далее. Функция run() никогда не завершается.

Такая схема может показаться очень непривычной, особенно потому, что функции schedule() и run() нигде в программе не определены. Вам придется просто поверить, что они работают так, как описано. Это службы, предоставляемые какой-либо уже существующей системой планирования. Хуже то, что read_data() и print_data() выполняются без какого-либо особого порядка. В действительности read_data() вызывается в два раза чаще (каждые полсекунды, а print_data() - только каждую секунду).

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

В Mozilla можно создать такой планировщик самостоятельно, либо использовать уже существующий и добавлять к нему то, что должно выполняться, но ни один из этих подходов не является распространенной практикой. Платформа Mozilla обо всем позаботится сама. В ней есть встроенный планировщик и функции, проверяющие все возможные виды пользовательского ввода и вывода. Нажатие клавиши - один тип ввода; скрипт на JavaScript - другой; часть HTML, получаемая от web-сервера - третий. Запланировать можно и небольшое событие (щелчок мышью), и что- то очень значительное (перерисовать весь HTML-документ). Все в Mozilla - запланированная функция, даже если этот факт не различить под нагромождением повседневных функций.

Иногда эта система планирования открыта для программиста. Листинг 6.4 очень похож на листинг 6.3; но это корректный и рабочий скрипт на JavaScript. Включите его в любой HTML- или XML-документ, вводите что- нибудь в регулярно появляющееся окно ввода и наблюдайте за заголовком окна - вы увидите, как работает система планирования. Этот пример работает достаточно медленно, чтобы при желании можно было, щелкнув мышью, закрыть окна.

function read_data() {
  if (!window.userdata)
    window.userdata = prompt("Введите новый заголовок");
}
function print_data() {
  if (window.userdata) {
    window.title = window.userdata;
    window.userdata = null; 
  }
} 
window.setInterval(read_data, 5000); 
window.setInterval(print_data, 100);
Листинг 6.4. Пример планирования вызова функций с помощью setInterval().

Хотя эти две функции и вызываются с разной частотой, система работает. print_data() будет часто проверять, есть ли для нее работа; read_data() будет запрашивать данные у пользователя, но не так часто.

Этот пример можно упростить. Функцию read_data() можно заставить менять заголовок каждый раз, когда она получает ввод. В таком случае от print_data() можно избавиться совсем. Такое изменение показывает, что меньшего числа планируемых функций можно достичь, если делать сами функции больше. Обратное также верно. Число планируемых функций зависит только от решения программиста на этапе проектирования.

На листинг 6.4 можно взглянуть с другой точки зрения. Вместо того чтобы удивляться тому, что две функции работают согласованно, но вместе с тем и независимо, можно подумать о том, какие роли играют эти две функции в среде исполнения. read_data() получает новую информацию и делает ее доступной. С точки зрения среды исполнения это поставщик данных. print_data() берет доступную информацию и, обрабатывая, перемещает ее куда-то еще. Это потребитель данных.

Поставщики и потребители - очень важные концепции проектирования, и в Mozilla мы будем с ними периодически сталкиваться. Идея пары поставщика и потребителя, работающих вместе, очень распространена, и эта пара связана одной целью: желанием обработать какой-то тип данных. Иногда эта пара просто находится в ожидании. Когда появляется какой- то стимул, она берется за дело.

Дальнейшее редактирование листинга 6.4 может включать в себя смену вызовов setInterval() на setTimeout(). setTimeout() позволяет запускать переданную ей функцию только раз, а не регулярно. После единственного запуска эта передаваемая функция удаляется из системы планирования и больше там не появляется. Функции, появляющиеся в системе планирования только раз, - особый случай.

Представим, что система планирования состоит только из элементов, применяемых лишь один раз. Далее предположим, что у всех этих элементов задержка перед выполнением нулевая. Наконец, предположим, что все эти элементы - поставщики, при каждом запуске они будут предоставлять какой-то кусок данных. В таких условиях система планирования является простой очередью событий. Когда поставщик в такой очереди запускается, событие инициируется. Если говорить нестрого, то поставщик в очереди называется событием, но данные, производимые этим поставщиком, называются событием. Так как у всех поставщиков в очереди нулевая задержка перед выполнением, Mozilla будет стараться из всех сил, чтобы выполнить их все сразу, то есть очистить очередь почти немедленно. Такая система полезна, например, для оповещения о нажатиях клавиш.

В случае листинга 6.4 создание поставщиков и потребителей данных - обязанность автора приложения. Очередь событий может быть организована и по-другому. Можно сделать так, чтобы добавление событий в очередь производилось платформой Mozilla. Кроме того, можно сделать ее обязанностью поиск подходящего потребителя при инициации события поставщика. Автор приложения может написать собственные потребители, которые будут работать с созданными событием данными, и сообщить Mozilla, что эти потребители заинтересованы в данных определенных событий.

Автор приложения называет такие потребители обработчиками событий, а Mozilla называет их подписчиками. Подписчик - это шаблон проектирования ПО - хорошо проработанная идея, повод подумать при проектировании. Часть кода, реализующая подписчик, ожидает информацию от другой части кода и реагирует на ее появление. Все скрипты JavaScript - подписчики, которые запускаются в ответ на какое-то событие, хотя на первый взгляд никаких событий нет.

Это краткое введение мы начали с простого примера на языке третьего поколения, а закончили системой, управляемой событиями. Именно так работает большая часть Mozilla. В частности, так, например, обрабатывается пользовательский ввод. В течение обсуждения мы коснулись понятий потребителя, поставщика, планирования, подписчика и расчета времени, а эти понятия сами по себе очень полезны. Когда наступит время для тяжелой работы и придется пользоваться XPCOM-компонентами, эти сведения очень пригодятся. Сейчас же все, что вам нужно - поверить, что в Mozilla вся обработка управляется событиями, даже если единственный обработчик - один большой скрипт.

Из "Скрипты" , "Скрипты", нам известно, что большая часть платформы Mozilla доступна как XPCOM-интерфейсы и компоненты, и очереди событий - не исключение. Два компонента

@mozilla.org/event-queue;1 @mozilla.org/event-queue-service;1

и интерфейс

nsIEventQueue

позволяют создавать, запускать и останавливать очереди событий. Эти компоненты полезны только для тех приложений, которые сильно адаптируют Mozilla к своим целям.

Так как очереди событий в Mozilla находятся на таком низком уровне, они редко используются напрямую. Вместо этого Mozilla выстраивает поверх них несколько более простые в работе системы. Это именно та функциональность, от которой зависят разработчики приложений для Mozilla. Эти функции по-прежнему находятся "под землей" в том смысле, что они все равно с пользовательским вводом напрямую почти не работают. Разговор о том, как Mozilla обрабатывает события, мы продолжим в ходе изучения не менее пяти таких промежуточных систем.