Как работает слушатель событий?


125

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

Мой вопрос, независимо от языка программирования или ситуации, в которой он применяется, как работает прослушиватель событий?

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

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

Как работает слушатель событий?


34
Слушатель событий вообще не проверяет. Он вызывается, когда происходит событие, когда он «слушает» пожары.
Роберт Харви

13
Да, но как он «слушает», не будет ли он постоянно проверять?
Гэри Холидей

28
Нет. «Event Listener», вероятно, плохой выбор слов; это на самом деле не "слушать" вообще. Все, что делает прослушиватель событий, - это ожидает вызова при возникновении события, как и любой другой метод. Пока его не вызывают таким образом, он вообще ничего не делает.
Роберт Харви

28
Каждый раз, когда вы проверяете, нажата ли кнопка, это стоит вам тактов. Обработчик событий (слушатель) стоит только тогда, когда кнопка фактически нажата.
Роберт Харви

45
@RobertHarvey - не обязательно, поскольку «слушатели» все еще нуждаются в постоянном опросе на более низком уровне. Вы просто толкаете сложность от своего собственного уровня кода глубже до аппаратных прерываний или чего-то еще. И да, это обычно будет более эффективным, но не потому, что прослушивание превосходит опрос, а потому, что опрос на более низком уровне более эффективен, чем опрос из C # и 15 уровней абстракции между вами и оборудованием.
Давор Адрало

Ответы:


140

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

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

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

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


54
Возможно, стоит упомянуть причину, по которой плата не нажимается, потому что кнопки являются «специальными», компьютер имеет прерывания и другие специальные возможности, которые может использовать ОС, которые абстрагированы в приложения пользовательского пространства.
WhatsName

46
@whatsisname, хотя это очень глубоко скрыто , на практике игровые движки, скорее всего, не работают с прерываниями, но на самом деле все еще опрашивают источник событий в цикле. Просто этот опрос централизован и оптимизирован, так что добавление большего количества слушателей событий не добавляет дополнительного опроса и сложности.
gntskn

7
@PieterGeerkens Я думаю, что gntskn подразумевал, что в рамках цикла игрового движка есть шаг, который проверяет наличие любых выдающихся событий. События будут обрабатываться во время каждого цикла вместе со всеми остальными действиями, выполняемыми один раз за цикл. Не будет отдельного цикла для проверки событий.
Джошуа Тейлор

2
@Voo: Все больше причин не вдаваться в этот уровень детализации в этом посте.
Роберт Харви

2
@ Voo: я говорю о кнопках, таких как физические клавиши на клавиатуре и кнопки мыши.
whatsisname

52

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

Система событий реализована с использованием объектов событий, которые управляют списком подписчиков. Заинтересованные объекты (называемые подписчиками , слушателями , делегатами и т. Д.) Могут подписаться на получение информации о событии, вызвав метод, который присоединяется к событию, в результате чего событие добавляет их в свой список. Всякий раз , когда событие уволено (терминология может также включать в себя: называется , срабатывает , вызывается , запуск и т.д.), он вызывает соответствующий метод по каждому из абонентов, чтобы сообщить им о событии, проходя по любому контекстной информации , которую они должны понимать , что случилось.


38

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

Более длинное объяснение немного сложнее.

Откуда происходят клиентские события?

Каждое современное приложение имеет внутренний, обычно полузакрытый «цикл событий», который отправляет события нужным компонентам, которые должны их получать. Например, событие «щелчок» отправляется кнопке, поверхность которой видна в текущих координатах мыши. Это на простейшем уровне. На самом деле ОС выполняет большую часть этой диспетчеризации, так как некоторые события и некоторые компоненты будут получать сообщения напрямую.

Откуда происходят события приложения?

Операционные системы отправляют события по мере их возникновения. Они делают это реактивно, будучи уведомленными их собственными водителями.

Как драйверы генерируют события?

Я не эксперт, но наверняка некоторые используют прерывания ЦП: аппаратное обеспечение, которым они управляют, повышает нагрузку на ЦП при появлении новых данных; CPU запускает драйвер, который обрабатывает входящие данные, которые в конечном итоге генерируют (очередь) событий для отправки, а затем возвращают управление обратно в ОС.

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


Существуют заметные исключения, например, игры на один раз, которые могут изменить ситуацию


10
Этот ответ объясняет, почему в браузере не происходит опрос событий щелчка мыши. Аппаратное обеспечение генерирует прерывание => драйвер разрешает его для события ОС => браузер разрешает его для события DOM => Механизм JS запускает прослушиватель для этого события.
Тибос

@Tibos afaict это также относится к событиям клавиатуры, событиям таймера, событиям рисования и т. Д.
Sklivvz

19

терминология

  • событие : тип вещей, которые могут произойти.

  • запуск события : конкретное возникновение события; событие происходит.

  • слушатель события : что-то, что высматривает события.

  • обработчик события : то, что происходит, когда слушатель события обнаруживает срабатывание события.

  • подписчик события : ответ, который должен вызвать обработчик события.

Эти определения не зависят от реализации, поэтому они могут быть реализованы разными способами.

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

Общие сценарии

  1. Программирование-логика событий.

    • Событие , когда какой - либо метод вызывается.

    • Обжиг события является конкретным вызовом к этому методу.

    • Прослушиватель событий является крюк в методе события , который называется на каждом выстреле событий , который вызывает обработчик события.

    • Обработчик событий вызывает набор абонентов событий.

    • Подписчик события (ы) выполнять любые действия (s) система означает , что произойдет в ответ на возникновение события.

  2. Внешние события.

    • Событие является внешним событием , которое может быть выведено из наблюдаемых.

    • Стрельбы события , когда , что внешнее событие может быть признаны , имевшее место.

    • Слушатель события как - то обнаруживает пуски события, часто путем опроса наблюдаемой (s), то он вызывает обработчик события при обнаружении события стрельбы.

    • Обработчик событий вызывает набор абонентов событий.

    • Подписчик события (ы) выполнять любые действия (s) система означает , что произойдет в ответ на возникновение события.

Опрос и вставка хуков в механизм запуска события

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

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

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

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

Логика цепочки событий

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

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

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


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

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

Как работает слушатель событий?

Как вы и подозревали, событие может работать через опрос. И если событие каким-то образом связано с внешними событиями, например нажатием клавиши клавиатуры, опрос должен произойти в какой-то момент.

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

Обновление: на аппаратном опросе низкого уровня

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

Интересно, что « прерывания » являются довольно обязательными, синхронными вещами, поэтому они не обрабатывают специальные сетевые топологии. Чтобы исправить это, « прерывания » были обобщены в асинхронные высокоприоритетные пакеты, называемые « транзакциями прерывания » (в контексте USB) или « прерываниями с сигналом сообщения » (в контексте PCI). Этот протокол описан в спецификации USB:

введите описание изображения здесь

- « Рисунок 8-31. Конечный автомат хоста транзакции OUT / Управление / Прерывание » в «Спецификации универсальной последовательной шины, редакция 2.0» , print-page-222; PDF-страница-250 (2000-04-27)

Суть в том, что устройства ввода-вывода и коммуникационные компоненты (например, концентраторы USB) в основном действуют как сетевые устройства. Итак, они отправляют сообщения, что требует опроса их портов и тому подобное. Это устраняет необходимость в выделенных аппаратных линиях.

Операционные системы , такие как Windows , кажется, обрабатывать сам процесс опроса, например , как описано в документации MSDN для USB_ENDPOINT_DESCRIPTOR«с , который описывает , как управлять тем, как часто для Windows опрашивает контроллер USB хост для прерывания / изохронными сообщений:

bIntervalЗначение содержит интервал опроса для прерывания и изохронных конечных точек. Для других типов конечных точек это значение следует игнорировать. Это значение отражает конфигурацию устройства в прошивке. Водители не могут изменить это.

Интервал опроса вместе со скоростью устройства и типом хост-контроллера определяют частоту, с которой драйвер должен инициировать прерывание или изохронную передачу. Значение в bIntervalне представляет фиксированное количество времени. Это относительное значение, и фактическая частота опроса также будет зависеть от того, работают ли устройство и хост-контроллер USB на низкой, полной или высокой скорости.

- «Структура USB_ENDPOINT_DESCRIPTOR» , Центр разработки аппаратного обеспечения, Microsoft

Новые протоколы подключения монитора, такие как DisplayPort, похоже, делают то же самое:

Многопотоковый транспорт (MST)

  • MST (многопотоковый транспорт) добавлен в DisplayPort Ver.1.2

    • В версии 1.1a был доступен только SST (однопотоковый транспорт)
  • MST транспортирует несколько потоков аудио / видео через один разъем

    • До 63 потоков; не «Стрим на Лейн»

      • Синхронность не предполагается среди этих транспортируемых потоков; один поток может быть в период гашения, в то время как другие не
    • Транспорт, ориентированный на соединение

      • Путь от источника потока до приемника целевого потока, установленного через транзакции сообщений через каналы AUX до начала передачи потока

      • Добавление / удаление потока, не затрагивая остальные потоки

введите описание изображения здесь

-Слайд 14 из «Обзор DisplayPortTM Ver.1.2» (2010-12-06)

Эта абстракция допускает некоторые полезные функции, такие как запуск 3 мониторов из одного соединения:

DisplayPort Multi-Stream Transport также позволяет соединять три или более устройств вместе, но в противоположной, менее ориентированной на потребителя конфигурации: одновременное управление несколькими дисплеями из одного выходного порта.

- "DisplayPort" , Википедия

Концептуально, суть в том, что механизмы опроса допускают более обобщенную последовательную связь, что замечательно, когда вам нужна более общая функциональность. Таким образом, аппаратное обеспечение и ОС делают большой опрос для логической системы. Затем потребители, которые подписываются на события, могут наслаждаться теми деталями, которые обрабатываются для них системой более низкого уровня, без необходимости писать свои собственные протоколы опроса / передачи сообщений.

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


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

Тем не менее, ваши аналогии с доставкой почты именно так, как я бы объяснил активность более высокого уровня.
Бармар

1
@ Barmar Я знаю, что когда устройства перешли на USB-соединения, было много разговоров о том, как они перешли от прямого генерирования прерываний (как это делает клавиатура PS / 2) к требованию опроса (как это делает клавиатура USB), и некоторые источники утверждают, что что опрос сделан процессором. Но другие источники утверждают, что это делается на специализированном контроллере, который преобразует опрос в прерывание для процессора.
Nat

@ Barmar Вы случайно не знаете, что правильно? Я, вероятно, видел больше источников, утверждающих, что процессор выполняет опрос, чем в противном случае, но специализированный контроллер для этого, кажется, имеет больше смысла. Я имею в виду, я думаю, что Arduino и другие встроенные устройства, как правило, требуют ЦП для выполнения опроса, но я не знаю об устройствах x86-типа.
Nat

1
Если кто-то может подтвердить, чтобы я мог обновить этот ответ, я думаю, что современные устройства ввода-вывода, например, те, которые подключены через USB, напрямую записывают в память , минуя управление процессором (и поэтому они быстры / эффективны и обеспечивают безопасность время от времени опасность ). Затем современная ОС требует опроса памяти, чтобы проверить наличие новых сообщений.
Nat

8

Тянуть против толчка

Есть две основные стратегии, чтобы проверить, произошло ли событие или достигнуто определенное состояние. Например, представьте, что вас ждет важная доставка:

  • Потяните : каждые 10 минут заходите в свой почтовый ящик и проверяйте, доставлен ли он,
  • Нажми : скажи парню, чтобы он позвонил тебе, когда они делают доставку.

Метод извлечения (также называемый опросом) проще: вы можете реализовать его без каких-либо специальных функций. С другой стороны, это часто менее эффективно, так как вы рискуете делать дополнительные проверки, не показывая им ничего.

С другой стороны, push- подход обычно более эффективен: ваш код запускается только тогда, когда ему есть чем заняться. С другой стороны, требуется, чтобы у вас был механизм для регистрации слушателя / наблюдателя / обратного вызова 1 .

1 К сожалению, моему почтальону обычно не хватает такого механизма.


1

О единстве в отдельности - нет другого способа проверить ввод игрока, кроме как опросить его каждый кадр. Чтобы создать прослушиватель событий, вам все равно понадобится такой объект, как «система событий» или «менеджер событий», чтобы выполнить опрос, поэтому он только перенесет проблему в другой класс.

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

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


Я бы не рассматривал центральный цикл обработки событий как оптимизацию, а как создание более читабельного, понятного кода, а не опроса, распространяющегося по всей базе кода. Это также позволяет «синтетическим» событиям и событиям не исходить из опроса игрового движка.
Блэкджек,

@ BlackJack Я согласен, и я обычно сам так кодирую, но ОП спросил о производительности. Кстати, в Unity на удивление много таких сомнительных решений по проектированию кода, как, например, наличие статических функций почти везде.
знаю

1

Если у вас нет какой-либо поддержки в вашей ОС / платформе, которая обрабатывает такие события, как нажатие кнопки, переполнение таймера или прибытие сообщения, - вам все равно придется реализовать этот шаблон прослушивателя событий, используя опрос (где-то внизу).

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

  1. Код выглядит чище и более изолированным (если он реализован правильно, конечно)
  2. Код, основанный на обработчиках событий, лучше переносит изменения (поскольку обычно вы модифицируете только некоторые обработчики событий)
  3. Если вам случится перейти на платформу с поддержкой нижележащих событий - вы можете повторно использовать существующие обработчики событий и просто избавиться от кода опроса.

Вывод - вам повезло поучаствовать в дискуссии и выучили одну альтернативу опросу. Ищите возможность применить эту концепцию на практике, и вы оцените, насколько элегантным может быть код.


1

Большинство циклов событий построены над примитивом мультиплексирования с опросом, предоставляемым операционной системой. В Linux этот примитив часто является системным вызовом ( poll2) (но может быть и старым select). В приложениях с графическим интерфейсом сервер дисплея (например, Xorg или Wayland ) связывается (через сокет (7) или канал (7) ) с вашим приложением. Читайте также о системных протоколах и архитектуре X Window .

Такие примитивы опроса эффективны; ядро на практике разбудит ваш процесс, когда будет сделан некоторый ввод (и обработано некоторое прерывание).

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

(Я упрощаю; на самом деле все гораздо сложнее)


1

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

Если многие вещи, которые будут отслеживать приложения, довольно похожи, может быть более эффективно иметь централизованную подсистему мониторинга - возможно, управляемую таблицей - которая может наблюдать за многими вещами и наблюдать, изменилась ли какая-либо из них. Например, если имеется 32 коммутатора, у платформы может быть функция считывать все 32 коммутатора одновременно в одно слово, что позволяет коду монитора проверять, изменились ли какие-либо коммутаторы между опросами и - если нет - нет беспокоиться о том, какой код может быть им интересен.

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


0

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

Всегда есть два важных данных: момент, когда событие происходит, и объект, где это событие происходит. Другой аргумент - больше данных о том, что случилось.

Слушатель события определяет реакцию на то, что происходит.


0

Слушатель событий следует шаблону публикации / подписки (как подписчик)

В своей простейшей форме объект публикации поддерживает список инструкций подписчиков, которые необходимо выполнить, когда что-то нужно опубликовать.

У него будет какой-то subscribe(x)метод, где x зависит от того, как обработчик события предназначен для обработки события. Когда вызывается подписка (х), х добавляется в список издателей инструкций / ссылок подписчиков.

Издатель может содержать все, некоторые или никакие из логики для обработки события. Это может просто потребовать ссылки на подписчиков, чтобы уведомить / преобразовать их с его определенной логикой, когда происходит событие. Он может не содержать логику и требовать подписчиков объектов (методов / прослушивателей событий), которые могут обработать событие. Скорее всего, они содержат смесь обоих.

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

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

Примеры

Для примера прослушивателя событий вы предоставляете метод / функцию / инструкцию / прослушиватель событий для метода subscribe () обработчика событий. Обработчик событий добавляет метод в свой список обратных вызовов подписчика. Когда происходит событие, обработчик события перебирает свой список и выполняет каждый обратный вызов.

Например, когда вы подписываетесь на новостную рассылку Stack Exchange, ссылка на ваш профиль будет добавлена ​​в таблицу подписчиков базы данных. Когда придет время публиковать новостную рассылку, ссылка будет использоваться для заполнения шаблона новостной рассылки и будет отправлена ​​на вашу электронную почту. В этом случае x - это просто ссылка на вас, а у издателя есть набор внутренних инструкций, используемых для всех подписчиков.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.