В информатике цикл событий является программной конструкцией или шаблон проектирования, который ожидает и отправляет события или сообщения в программе. Цикл событий работает, отправляя запрос некоторому внутреннему или внешнему «провайдеру событий» (который обычно блокирует запрос до прибытия события), а затем вызывает соответствующий обработчик событий (« отправляет событие "). Цикл событий также иногда называют диспетчером сообщений, цикл сообщений, насос сообщений или цикл выполнения .
Цикл событий может использоваться вместе с реактором , если провайдер событий следует файловому интерфейсу, который можно выбрать или 'опросить' (системный вызов Unix, а не фактический опрос ). Цикл событий почти всегда работает асинхронно с отправителем сообщения.
Когда цикл событий формирует центральную конструкцию потока управления программы, как это часто бывает, его можно назвать основным циклом или основным событием. цикл . Это название уместно, потому что такой цикл событий находится на самом высоком уровне управления в программе.
Сообщается, что подкачки сообщений «перекачивают» сообщения из очереди сообщений программы (назначенной и обычно принадлежащей базовой операционной системе) в программу для обработки. В самом строгом смысле цикл событий - это один из методов реализации межпроцессного взаимодействия. Фактически, обработка сообщений существует во многих системах, включая компонент уровня ядра в операционной системе Mach. Цикл событий - это особый метод реализации систем, которые используют передачу сообщений.
Этот подход отличается от ряда других альтернатив:
Из-за преобладания графических пользовательских интерфейсов большинство современных приложений имеют главный цикл. Подпрограмма get_next_message ()
обычно предоставляется операционной системой, и блокирует, пока сообщение не станет доступным. Таким образом, в цикл входит только тогда, когда есть что-то обработать.
function main initialize () while message! = Quit message: = get_next_message () process_message (message) endwhileend function
В Unix парадигма «все является файлом » естественным образом приводит к файловому циклу событий. Чтение и запись в файлы, межпроцессное взаимодействие, сетевая связь и управление устройствами - все это достигается с помощью файлового ввода-вывода с целью, идентифицированной дескриптором файла . Системные вызовы select и poll позволяют отслеживать набор файловых дескрипторов на предмет изменения состояния, например когда данные становятся доступны для чтения.
Например, рассмотрим программу, которая читает из постоянно обновляемого файла и отображает его содержимое в X Window System, которая взаимодействует с клиентами через сокет (либо домен Unix или Беркли ):
def main (): file_fd = open ("logfile.log") x_fd = open_display () construct_interface () while changed_fds == select ({file_fd, x_fd}) : if file_fd in changed_fds: data = read_from (file_fd) append_to_display (data) send_repaint_message () if x_fd in changed_fds: process_x_messages ()
Одна из немногих вещей в Unix, которая не соответствует файловый интерфейс - это асинхронные события (сигналы ). Сигналы принимаются в обработчиках сигналов, небольших ограниченных фрагментах кода, которые выполняются, пока остальная часть задачи приостановлена; если сигнал получен и обработан во время блокировки задачи в select ()
, select вернется раньше с EINTR ; если сигнал получен, когда задача привязана к ЦП, задача будет приостановлена между инструкциями до возврата обработчика сигнала.
Таким образом, очевидным способом обработки сигналов является установка для обработчиков сигналов глобального флага и проверка цикла событий на наличие флага непосредственно перед и после вызова select ()
; если он установлен, обрабатывать сигнал таким же образом, как и с событиями в файловых дескрипторах. К сожалению, это приводит к состоянию гонки : если сигнал поступает сразу между проверкой флага и вызовом select ()
, он не будет обработан до тех пор, пока select ()
возвращается по какой-либо другой причине (например, когда его прервал разочарованный пользователь).
Решение, к которому пришел POSIX, - это вызов ()
, который похож на select ()
, но требует дополнительного sigmask
параметр, описывающий маску сигнала. Это позволяет приложению маскировать сигналы в основной задаче, а затем удалять маску на время вызова select ()
, так что обработчики сигналов вызываются только тогда, когда приложение привязано к вводу-выводу. Однако реализации pselect ()
только недавно стали надежными; версии Linux до 2.6.16 не имеют системного вызова pselect ()
, заставляя glibc эмулировать его с помощью метода, подверженного тому же самому условию гонки pselect ()
призвано избежать.
Альтернативным, более переносимым решением является преобразование асинхронных событий в события на основе файлов с помощью, где «обработчик сигнала записывает байт в канал, другой конец которого отслеживается select ()
в основной программе ». В ядре Linux версии 2.6.22 был добавлен новый системный вызов signalfd ()
, который позволяет получать сигналы через специальный файловый дескриптор.
В операционной системе Microsoft Windows процесс, взаимодействующий с пользователем, должен принимать входящие сообщения и реагировать на них, которые почти неизбежно выполняется циклом сообщений в этом процессе. В Windows сообщение приравнивается к событию, которое создается и накладывается на операционную систему. Событием может быть взаимодействие пользователя, сетевой трафик, системная обработка, активность таймера, межпроцессное взаимодействие и другие. Для неинтерактивных событий, связанных только с вводом-выводом, Windows имеет порты завершения ввода-вывода. Циклы портов завершения ввода-вывода выполняются отдельно от цикла сообщений и не взаимодействуют с циклом сообщений из коробки.
«Сердцем» большинства Win32 приложений является функция WinMain (), которая вызывает GetMessage () в петле. GetMessage () блокируется до тех пор, пока не будет получено сообщение или «событие» (с функцией PeekMessage () в качестве неблокирующей альтернативы). После некоторой дополнительной обработки он вызовет DispatchMessage (), который отправит сообщение соответствующему обработчику, также известному как WindowProc. Обычно сообщения, не имеющие специального WindowProc (), отправляются в DefWindowProc, по умолчанию. DispatchMessage () вызывает WindowProc дескриптора сообщения (зарегистрированного с помощью функции RegisterClass () ).
Более поздние версии Microsoft Windows гарантируют программисту, что сообщения будут доставлены в цикл сообщений приложения в том порядке, в котором они были восприняты системой и ее периферийными устройствами. Эта гарантия очень важна при рассмотрении последствий разработки многопоточных приложений.
Однако некоторые сообщения имеют разные правила, например сообщения, которые всегда принимаются последними, или сообщения с другим задокументированным приоритетом.
X приложения, непосредственно использующие Xlib, построены на основе семейства функций XNextEvent
; XNextEvent
блокируется до тех пор, пока событие не появится в очереди событий, после чего приложение обработает его соответствующим образом. Цикл событий Xlib обрабатывает только события оконной системы; приложения, которым требуется возможность ожидания других файлов и устройств, могут создавать собственный цикл обработки событий из таких примитивов, как ConnectionNumber
, но на практике обычно используют многопоточность.
Очень немногие программы используют Xlib напрямую. В более общем случае наборы инструментов GUI, основанные на Xlib, обычно поддерживают добавление событий. Например, наборы инструментов, основанные на Xt Intrinsics, имеют XtAppAddInput ()
и XtAppAddTimeout ()
.
Обратите внимание, что вызывать функции Xlib из обработчика сигналов небезопасно, потому что приложение X могло быть прервано в произвольном состоянии, например в пределах XNextEvent
. См. [1] для решения для X11R5, X11R6 и Xt.
Цикл событий GLib изначально был создан для использования в GTK +, но теперь используется и в приложениях без графического интерфейса пользователя, например D-Bus. Опрашиваемый ресурс - это набор файловых дескрипторов, которые интересуют приложение; блок опроса будет прерван, если поступит сигнал или истечет тайм-аут (например, если приложение указало тайм-аут или задачу ожидания). Хотя GLib имеет встроенную поддержку для событий завершения файловых дескрипторов и дочерних элементов, можно добавить источник событий для любого события, которое может быть обработано в модели подготовки-проверки-отправки. [2]
Библиотеки приложений которые построены на цикле событий GLib, включают GStreamer и методы асинхронного ввода-вывода из GnomeVFS, но GTK + остается наиболее заметным клиентская библиотека. События из оконной системы (в X, считываются из X сокета ) преобразуются GDK в события GTK + и выдаются как GLib сигналы об объектах виджетов приложения.
Допускается только один цикл CFRunLoop для каждого потока, и может быть подключено произвольно много источников и наблюдателей. Затем источники связываются с наблюдателями через цикл выполнения, при этом он организует очереди и отправку сообщений.
CFRunLoop абстрагируется в Какао как NSRunLoop, что позволяет любому сообщению (эквивалентному вызову функции в не- отражающей среде выполнения) ставиться в очередь для отправки в любой объект.