Связь между компонентами с помощью событий — одна из фундаментальных парадигм программирования. Один модуль публикует событие, а другие модули отвечают через прослушиватели. Это асинхронная модель, которая обеспечивает отличное разделение ответственности, но при этом позволяет нескольким компонентам работать вместе. Помимо сообщений, он также представляет диспетчер, который является полезным механизмом общего назначения.

Почему?

Современные приложения работают в активной среде. Данные приходят из сети. Пользователь нажимает кнопку мыши или проводит пальцем по экрану. Другая программа выходит на передний план, отодвигая текущую на задний план. GPS и акселерометры обновляют положение устройства. Запланированный будильник срабатывает в запрошенное время.

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

Эта статья посвящена преимуществам и основным качествам программирования мероприятий. Как и во всех парадигмах, существуют ограничения. Я посмотрю на них в следующей статье.

Издатель

Для каждого события требуется издатель, компонент, который его рекламирует, может вызвать конкретное событие.

Здесь у нас есть служба Keyboard, которая вызывает события KeyPressed и KeyReleased, когда пользователь взаимодействует с клавиатурой. KeyMessage — это тип сообщения, которое он генерирует для события. Сообщения включают соответствующие сведения о событии. В этом случае мы ожидаем код нажатой клавиши и, возможно, метку времени.

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

Слушатель

Кто интересуется клавиатурой подписывается на эти события.

Когда пользователь нажимает клавишу, вызывается функция OnKeyPressed. Эта функция принимает KeyMessage в качестве аргумента, который предоставляет ей нажатую клавишу.

Модуль подписки может прослушивать события от нескольких разных издателей. Возможно, он также прослушивает событие Pointer.Pressed или Gamepad.ButtonPressed. Слушатель должен иметь дело с получением сообщений от всех событий в любое время. Сам по себе он ничего не выполнит. Он остается неактивным, пока не получит сообщение.

Необязательно

Хотя приведенный выше синтаксис в некоторой степени императивен по своей природе, одним из приятных аспектов программирования событий является то, что он хорошо работает с несколькими парадигмами. Мы могли бы использовать его в декларативном синтаксисе пользовательского интерфейса:

Или это может служить основой для реактивной модели:

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

Диспетчер событий и цикл

Для программирования событий требуется диспетчер. Издателям нужно где-то публиковать свои сообщения. Диспетчер может быть основной частью ОС, как классическая функция Windows PostMessage, или может быть библиотечным средством. В любом случае каждый издатель подключается к этому диспетчеру. Точно так же каждый прослушиватель подписывается на диспетчер, чтобы он мог получать сообщения. Во многих API фактический механизм диспетчеризации несколько скрыт, и издателям и подписчикам не нужно много думать об этом.

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

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

Например, в NodeJS это отображается как функция nextTick. Для Fuse я добавил функцию UpdateManager.AddDeferredAction.

Размытые линии

Некоторые API или языки, такие как C#, предоставляют синхронные «события»: они обрабатываются немедленно без цикла обработки событий. Это слабая форма парадигмы событий, поскольку синхронный характер делает ее небезопасной. Слушатель выполняется в тот момент, когда издатель отправляет сообщение, поэтому издатель должен опасаться, что слушатель может перезвонить издателю. Чтобы быть в безопасности, это требует ограниченной обработки сообщений или работы с повторным входом — что-то, что делать не очень приятно. Возможность выйти за пределы стека издателя — огромное преимущество модели асинхронных событий.

Модель синхронизации также может неизбежно привести к тому, что слушатели будут делать ложные предположения о порядке событий и глобальном состоянии. Например, прослушиватель клавиатуры может просмотреть текущее состояние Keyboard, а не просто использовать предоставленные свойства KeyMessage. Асинхронная модель лучше обеспечивает разделение компонентов.

Целевые сообщения и удаленное выполнение также можно спутать с парадигмой событий. Рассмотрим сообщение, которое создается, а затем отправляется непосредственно одному потребителю либо локально, либо по сети. Это звучит как описание императивного программирования. Действительно, Objective-C относится к вызову функции как к передаче сообщения. Это не парадигма программирования событий.

Используйте его везде

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

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