Системы, управляемые событиями

В начале 70-х годов появилась новая архитектура многозадачных систем довольно резко отличающаяся от вышеописанной модели последовательных процессов. Речь идет о так называемых системах, управляемых событиями (event-driven systems).
На первый взгляд, концепция систем, управляемых событиями, близко родственна гармонически взаимодействующим процессам. Во всяком случае, одно из ключевых понятий этой архитектуры, очередь событий, мы упоминали в числе средств гармонического межпоточного взаимодействия. Различие между этими архитектурами состоит, скорее, во взгляде на то, что представляет собой программа.
В модели гармонически взаимодействующих потоков процесс исполнения программного комплекса представляет собой совокупность взаимодействующих нитей управления. В системе, управляемой событиями, программа представляет собой совокупность объектов, обменивающихся сообщениями о событиях, а также реагирующих на сообщения, приходящие из внешних источников.
В идеале, объекты взаимодействуют между собой только через сообщения. Приходящие сообщения побуждают объект изменить свое состояние и, возможно, породить некоторое количество сообщений, предназначенных для других объектов. При такой модели взаимодействия нам неважно, исполняются ли методы объектов как параллельные (или псевдопараллельные) нити, или же последовательно вызываются единой нитью, менеджером или диспетчером сообщений.
Впервые эта архитектура была реализована в экспериментальных настольных компьютерах Alto, разработанных в 1973 году в исследовательском центре PARC фирмы Xerox. Целью эксперимента было создание операционной среды, удобной для создания интерактивных программ с динамичным пользовательским интерфейсом.
В этих системах впервые была реализована многооконная графика, когда пользователь одновременно видит на экране графический вывод нескольких программ и может активизировать любую из них, указав на соответствующее окно при помощи манипулятора-"мыши".
При каждом движении мыши, нажатии на ее кнопки или клавиши на клавиатуре генерируется событие. События могут также генерироваться системным таймером или пользовательскими программами. Нельзя не упомянуть "визуальные" события, которые порождаются в ситуации, когда пользователь
сдвинул или закрыл одно из окон и открыл при этом часть окна, находившегося внизу. Этому окну посылается событие, говорящее о том, что ему нужно перерисовать часть себя (рис. 7.10).

Рис. 7.10. Визуальное событие

Каждое сообщение о событии представляет собой структуру данных, которая содержит код, обозначающий тип события: движение мыши, нажатие кнопки и т. д., а также поля, различные для различных типов событий. Для "мышиных" событий — это текущие координаты мыши и битовая маска, обозначающая состояние кнопок (нажата/отпущена). Для клавиатурных событий — это код нажатой клавиши, обычно, ASCII-код символа для алфавитно-цифровых и специальные коды для стрелок и других "расширенных" и "функциональных" клавиш — и битовая маска, обозначающая состояние различных модификаторов, таких как SHIFT, CNTRL, ALT и т. д. Для визуальных событий — это координаты прямоугольника, который нужно перерисовать, и т. д.
Все сообщения о событиях помещаются в очередь в порядке их возникновения.
В системе существует понятие обработчика событий. Обработчик событий Представляет собой объект, т. е. структуру данных, с которой связано несколько подпрограмм — методов. Один из методов вызывается при поступлении сообщения. Обычно он также называется обработчиком событий. Некоторые системы предлагают объектам-обработчикам предоставлять различные методы для обработки различных событий — например, метод onClick будет вызываться, когда придет событие, сигнализирующее о том что кнопка мыши была нажата и отпущена, когда курсор находился над областью, занимаемой объектом на экране.
Рассмотрим объект графического интерфейса, например меню. При нажатии на кнопку мыши в области этого меню вызывается обработчик события Он разбирается, какой из пунктов меню был выбран, и посылает соответствующее командное сообщение объекту, с которым ассоциировано меню Этот объект, в свою очередь, может послать командные сообщения каким-то другим объектам. Например, если была выбрана команда File/Open, меню передаст обработчику основного окна приложения сообщение FILEOPEN, а тот, в свою очередь, может передать команду Open объекту, отвечающему за отрисовку и обработку файлового диалога.
Таким образом, вместо последовательно исполняющейся программы, время от времени вызывающей систему для исполнения той или иной функции, мы получаем набор обработчиков, вызываемых системой в соответствии с желаниями пользователя. Каждый отдельный обработчик представляет собой конечный автомат, иногда даже вырожденный, не имеющий переменной состояния. Код обработчика и по реализации обычно похож на конечный
автомат, и состоит m большого оператора switch, выбирающего различные действия в зависимости от типа пришедшего сообщения (пример 7.8).

Пример 7.8. Обработчик оконных событий в OS/2 Presentation Manager

/* фрагмент примера из поставки IBM Visual Age for C++ 3.0.
* Обработчик событий меню предоставляется системой,
* а обработку командных событий, порождаемых меню,
* вынужден брать на себя разработчик приложения. */
/****************************************************************
Copyright (С) 1992 IBM Corporation
ОТКАЗ ОТ ГАРАНТИЙ. Следующий код представляет собой пример кода созданный IBM Corporation. Этот пример кода не является частью ни одного стандарта или продукта IBM и предоставляется вам с единственной целью — помочь в разработке ваших приложений. Код предоставляется "КАК ЕСТЬ", без каких-либо гарантий. IBM не несет ответственности за какие бы то ни было повреждения, возникшие в результате использования вами этого кода, даже если она и могла предполагать возможность таких повреждений.

/****************************************************************
* Имя: MainWndProc *
Описание: Оконная процедура главного окна клиента. *
* Концепции: Обрабатывает сообщения, посылаемые главному
окну клиента. Эта процедура обрабатывает основные сообщения, которые должны обрабатывать все клиентские окна, и передает все остальные [функции] UserWndProc, в которой разработчик может обработать любые другие
сообщения. *
API: He используются
* Параметры: hwnd — Идентификатор окна, которому адресовано сообщение
* msg — Тип сообщения
* mpl — Первый параметр сообщения
* тр2 — Второй параметр сообщения.
* Возвращаемое значение: определяется типом сообщения
*/
****************************************************************
MRESULT EXPENTRY MainWndProc(HWND hwnd, USHORT msg, MPARAM mpl,
MPARAM mp2)
{
switch(msg) {
case WM_CREATE:
return(InitMainWindow(hwnd, mpl, mp2)};
break;
case WM_PAINT:
«
MainPaint(hwnd); break;
case WM_COMMAND:
MainCommand(mpl,. mp2); break;
case WM_INITMENU:
Ini tMenu(mpl, mp2); break;
case HM_QUERY_KEYS_HELP:
return (MRESULT)PANEL_HELPKEYS;/* Вернуть Id панели подсказки ' break;
/*
* Все необработанные сообщения передаются
* пользовательской процедуре окна.
* Она отвечает за передачу всех необработанных
* сообщений функции WinDefWindowProc(); */
default:
return(UserWndProc(hwnd, msg, mpl, mp2)); break;
return (MRESULT)O; /* Все оконные процедуры должны по умолчанию возвращать 0 */
} /* Конец MainWndProc() */

/****************************************************************
* Имя: MainCommand
* Назначение: Главная процедура окна, обрабатывающая WM_COMMAND *
* Концепции: Процедура вызывается, когда сообщение WM_COMMAND
* отправляется главному окну. Оператор switch
* переключается в зависимости от id меню, которое
* породило сообщение и предпринимает действия,
* соответствующие этому пункту меню. Все id меню,
* не являющиеся частью стандартного набора команд,
* передаются пользовательской процедуре обработки
* WM_COMMAND.
* API : WinPostMsg *
* Параметры : mpl — Первый параметр сообщения
тр2 — Второй параметр сообщения *
* Возвращает: VOID *
\*************+**************************************************^
VOID MainCommand(MPARAM mpl, MPARAM mp2)
switch(SHORT1FROMMP(mpl))
I
case IDM_EXIT:
WinPostMsg( hwndMain, WM_QUIT, NULL, NULL break;
case IDM__FILENEW: FileNew(mp2); break;
case IDM_FILEOPEN: FileOpen(mp2); break;
case IDM_FILESAVE: FileSave(mp2); break;
case IDM_FILESAVEAS: FileSaveAs(mp2); break;
case IDM_EDITUNDO: EditUndo(mp2); break;
case IDM_EDITCUT: EditCut(mp2); break;
case IDM_EDITCOPY: EditCopy(mp2); break;
case IDM_EDITPASTE: EditPaste(mp2); break;
case IDM_EDITCLEAR: EditClear(mp2); break;
case IDM_HELPUSINGHELP: HelpUsingHelp(mp2); break;
case IDM_HELPGENERAL: HelpGeneral(mp2); break;
case IDM_HELPKEYS: HelpKeys(mp2); break;
case IDM_HELPINDEX: Helplndex(mp2); break;
case IDM_HELPPRODINFO: HelpProdInfo(mp2); break; /*
* Здесь вызывается пользовательская процедура
* обработки команд, чтобы обработать те id',
* которые еще не были обработаны. */
default:
UserCammand(mpl, mp2); break; )
} /* MainCommand() */


Специальная программа, менеджер событий (рис. 7.12), просматривает очередь и передает поступающие события обработчикам. События, связанные с экранными координатами, передаются обработчику, ассоциированному с соответствующим окном. Клавиатурные события передаются фокусу клавиатуры — текущему активному обработчику клавиатуры.
Управление событиями позволяет относительно легко разрабатывать динамичные пользовательские интерфейсы, привычные для пользователей современных графических оболочек.
Высокая динамичность интерфейса проще всего обеспечивается, если каждый обработчик быстро завершается. Если же в силу природы запрашиваемой операции она не может завершиться быстро — например, если мы вставили символ, параграф удлинился на строку, и в результате текстовому процессору Типа WYSIWYG приходится переформатировать и переразбивать на страни-Цы весь последующий текст — мы можем столкнуться с проблемой.

Рис. 7.12. Менеджер и обработчики событий

В такой ситуации (а при написании реальных приложений она возникает сплошь и рядом) мы и вынуждены задуматься о том, что же в действительности представляют собою обработчики — процедуры, синхронно вызываемые единственной нитью менеджера событий, или параллельно исполняющиеся нити. Первая стратегия называется синхронной обработкой сообщений, а вторая, соответственно, — асинхронной.
Графические интерфейсы первого поколения — Mac OS, Winl6 — реализовывали синхронную обработку сообщений, а когда обработчик задумывался надолго, рисовали неотъемлемый атрибут этих систем — курсор мыши в форме песочных часов.
Несколько более совершенную архитектуру предлагает оконная подсистема OS/2, Presentation Manager. PM также реализует синхронную стратегию обработки сообщений (менеджер событий всегда ждет завершения очередного обработчика), но в системе, помимо менеджера событий, могут существовать и другие нити. Если обработка события требует длительных вычислений или других действий (например, обращения к внешним устройствам или к сети), рекомендуется создать для этого отдельную нить и продолжить обработку асинхронно. Если же приложение этого не сделает (например, обработчик события просто зациклится или заснет на семафоре), системная очередь сообщений будет заблокирована и ни одно из графических приложений не сможет работать. Современные версии РМ предоставляют в этом случае возможность отцепить "ненормальное" приложение от очереди или паже принудительно завершить его.
Асинхронные очереди сообщений предоставляют Win32 и оконная система X Window. Впрочем, и при асинхронной очереди впавший в философские размышления однопоточный обработчик событий — тоже малоприятное зрелище, ведь он не может перерисовать собственное окно, поэтому передвижение других окон по экрану порождает любопытные спецэффекты (к сожалению, запечатлеть эти эффекты при помощи утилит сохранения экрана невозможно — все известные автору средства дожидаются, пока все попавшие в сохраняемую область окна перерисуются. А фотографии монитора обычно имеют низкое качество). Разработчикам приложений для названных систем также рекомендуется выносить длительные вычисления в отдельные нити.
Большинство реальных приложений для современных ОС, имеющих пользовательский интерфейс, таким образом, имеют двух- или более слойную архитектуру. При этом архитектура ближайшего к пользователю стоя (frontend), как правило, тяготеет к событийно-управляемой, а следующие слои (backend) обычно состоят из более традиционных взаимодействующих (не всегда, впрочем, строго гармонически) параллельно исполняющихся нитей, зачастую даже разнесенных по разным вычислительным системам.