БЕСПЛАТНАЯ ПОДГОТОВКА К ЕГЭ ПО ПРОФИЛЬНОЙ МАТЕМАТИКЕ
Подготовься к ЕГЭ-2026 по профильной математике самостоятельно с помощью сервиса "1С:Репетитор"!
Понятная теория и эффективные тренажеры с объяснением! Вы успеете подготовиться к экзамену! Начните занятия прямо сейчас!
design_arrow
Событийно-ориентированное программирование

Событийно-ориентированное программирование

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

Теоретические основы

  1. Формальная модель событийной системы

    Рассмотрим систему как кортеж:

    Σ = (S, E, Q, H, δ, s0)

    где

    • S – множество состояний;

    • E – множество типов событий; элемент события – кортеж e = (type, payload, t) с моментом времени t;

    • Q – очередь событий (обычно FIFO, иногда – приоритетная);

    • H – набор обработчиков h: (s, e) → (s', out);

    • δ – функция диспетчеризации, выбирающая обработчик из H для e;

    • s0 – начальное состояние.

    Исполнение: события поступают в Q; диспетчер δ извлекает верхнее событие e, вызывает соответствующий обработчик h, который возвращает новое состояние s' и (опционально) порождает выходное событие/действие out. Цикл повторяется, пока Q не пуст.

  2. Конечные автоматы

    СОП естественно описывается детерминированными конечными автоматами (ДКА). Формально:

    M = (S, A, δ, s0, F)

    где A – алфавит входов (типы событий), F – подмножество принимающих состояний. Переход:

    δ: S × A → S

    На практике A – сигнатуры событий, δ – таблица переходов/обработчиков, S – режимы интерфейса (ожидание, ввод, подтверждение и т.д.).

  3. Модель времени и причинности

    Для корректности реакций важны:

    • Метка времени t(e) и порядок обработки (FIFO по t, либо приоритет с частичным порядком).

    • Дедлайны: событие считается просроченным, если now - t(e) > D_max.

    • Дебаунс/троттлинг: регуляризация бурстов (см. §4.5).

Архитектура исполнения: цикл, очередь, диспетчеризация

  1. Событийный цикл (event loop)

    Базовый алгоритм:

    state := s0

    Q :=

    while true do

      e := Q.pop()          // блокирующее ожидание или проверка

      h := δ(e.type)        // диспетчеризация по типу

      (state, out) := h(state, e)

      emit(out)             // генерация побочных эффектов/вторичных событий

    end

    Важные варианты:

    • Один поток исполнения (GUI, JS в браузере) – неблокирующие обработчики.

    • Пулы потоков (серверы) – параллельная обработка; требуется синхронизация состояния.

    • RT-loop (встроенные системы) – строгие тайминги, предсказуемая латентность.

  2. Очереди и приоритеты

    Типы Q:

    • FIFO для обычных событий;

    • приоритетная (min-heap/max-heap) – для таймеров и служебных сигналов;

    • кольцевая (ring buffer) – фиксированная емкость в системах реального времени.

    Оценки сложности:

    • push, pop в FIFO: O(1) амортизированно;

    • в куче: O(log n);

    • в кольцевом буфере при отсутствии переполнения: O(1).

  3. Варианты передачи управления и данных

    • Callbacks: функция обратного вызова f(e);

    • Promise/Future: контейнер будущего результата;

    • async/await: синтаксический сахар над корутинами;

    • Pub/Sub: декуплинг источников и подписчиков через шину событий

Языковые механизмы (JS, Python, C# – с ориентацией на экзаменационные задачи)

  1. JavaScript (однопоточный event loop)

    document.addEventListener('click', (ev) => {

      // обработчик короткий и неблокирующий

      queueMicrotask(() => console.log('clicked at', ev.clientX, ev.clientY));

    });

    Правило: никаких долгих вычислений в обработчиках UI; выносите тяжёлые задачи в Web Workers/сервер.

  2. Python (asyncio)

    import asyncio

    async def handler(e):

        # неблокирующая реакция

        await asyncio.sleep(0)  # уступить управление

        return f"handled {e}"

    async def main():

        events = ["A","B","C"]

        tasks = [asyncio.create_task(handler(e)) for e in events]

        for t in tasks:

            print(await t)

    asyncio.run(main())

    Правило: I/O – через await; CPU-связанные вычисления – в ProcessPoolExecutor/ThreadPoolExecutor.

  3.  C# (.NET events, async/await)

    public event EventHandler<DataEventArgs>? DataReady;

    private async Task OnTickAsync()

    {

        var data = await FetchAsync();           // неблокирующая операция

        DataReady?.Invoke(this, new DataEventArgs(data));

    }

    Правило: подписчики обязаны быть короткими и устойчивыми к исключениям; при необходимости – отмена CancellationToken.

Информатика–схема событийно-ориентированного программирования

Правила корректности и инженерные практики

  1. Чистота и детерминизм

    • Обработчик h(s, e) должен по возможности быть чистым: не обращаться к внешним изменяемым данным, возвращать новое состояние s'.

    • Детерминированность достигается фиксированным порядком диспетчеризации и явной сериализацией критических секций.

  2. Ограничение времени обработки

    Пусть T_h – время обработки события, λ – интенсивность поступления (событий/с). Условие устойчивости очереди:

    ρ = λ · T_h < 1

    Если ρ ≥ 1, очередь будет расти – требуется снижение T_h (оптимизация), ограничение λ (пропуск/сэмплинг), параллелизм.

  3.  Идемпотентность и повторная доставка

    Обработчик должен быть идемпотентным: повторная обработка того же e не меняет результат. Это устраняет классы ошибок при повторной доставке и восстановлении.

  4.  Контракты событий

    Событие документируют как тип:

    type: string

    payload: структура (поля, типы, единицы измерения)

    t: время (UTC, монотонные тики)

    id: уникальный идентификатор (для дедупликации)

    Нарушение контракта – источник расхождений состояний.

  5. Дебаунс и троттлинг

    • Дебаунс: исполняем обработчик, если событие сохранялось непрерывно K шагов/Δt времени.

    • Троттлинг: ограничиваем частоту вызовов не чаще 1/Δt.
      Эти техники стабилизируют бурсты и снижают λ.

  6.  Исключения, отмена, тайм-ауты

    • Любое ожидание снабжается тайм-аутом T_out.

    • Отмена – через токены/флаги; обработчики обязаны корректно завершаться.

    • Исключения не просачиваются за пределы обработчика: логирование, перевод в «безопасное» состояние.

  7.  Видимость и синхронизация

    В многопоточной диспетчеризации:

    • доступ к общему состоянию – через мьютексы/очереди сообщений;

    • для микрозадач – lock-free структуры (CAS), но только при строгом тест-плане.

Мини-шпаргалка (формулы и шаблоны)

  1. Устойчивость очереди:
    ρ = λ · T_h     // должно быть < 1

  2. Дебаунс по K отсчётам (скользящее окно из K битов):

    R := ((R << 1) | b) & ((1 << K) - 1)

    if R == (1 << K) - 1 then S := 1

    if R == 0 then S := 0

  3. Дедлайн события:
    isLate(e) := (now - t(e)) > D_max

  4. Таблица переходов ДКА:
    s' = δ[s, a]   // двумерная таблица по состоянию и входу

  5. Приоритетная диспетчеризация: событие с меньшим deadline имеет больший приоритет.

Типичные ошибки (и как их предотвратить)

  • Блокирующий обработчик в однопоточном цикле → «заморозка» UI/очереди. Решение: вынос CPU-нагрузки; разбиение на микрозадачи.
  • Потеря подписок/утечки: не отписываются обработчики → рост памяти. Решение: «слабые» подписки/RAII/using.
  • Гонки состояний при параллельной обработке. Решение: сериализация критических участков, очереди сообщений вместо разделяемой памяти.
  • Повторная доставка ломает бизнес-логику. Решение: идемпотентность + event.id + журналирование.
  • Переполнение очереди в бурстах. Решение: back-pressure (ограничение источника), троттлинг, деградация качества (сэмплинг).
  • Недиагностируемость: нет логов/метрик. Решение: структурированное логирование с event.id, t, latency.

Связь с подготовкой к ЕГЭ по информатике

  • Алгоритмы и структуры данных: реализация очереди (массив/кольцевой буфер), куча приоритетов, амортизированная сложность.
  • Логика и конечные автоматы: таблицы истинности и переходов, построение ДКА/НКА, минимизация состояний.
  • Кодирование информации: форматы событий (поля, типы), контроль чётности/контрольные суммы.
  • Системы счисления: побитовые операции в дебаунсе/масках состояний.
  • Оценка времени и ресурсов: проценты, средние величины, пропускная способность (λ, T_h, ρ).

Пять упражнений – с разбором

Упражнение 1. «Кольцевая очередь и устойчивость»

Условие. В систему приходят события со средней интенсивностью λ = 200 событий/с. Среднее время обработки одного события T_h = 3 мс.

  1. Проверить устойчивость.
  2. Какой минимальной ёмкости C должна быть кольцевая очередь, чтобы гарантировать отсутствие переполнения за интервал 1 с при кратковременном удвоении интенсивности до 2λ на промежутке Δt = 100 мс? Считать, что обработчик работает с неизменным T_h.

Решение.

  1. Устойчивость по формуле:
    ρ = λ · T_h = 200 · 0.003 = 0.6 < 1  → устойчива.
  2.  За «бурст» длительностью Δt приходит событий:

    N_in = 2λ · Δt = 400 · 0.1 = 40

    За то же время система успеет обработать:

    N_out = Δt / T_h = 0.1 / 0.003 ≈ 33.33 → 33 события

    Максимальный прирост в очереди:

    ΔQ = N_in − N_out ≈ 40 − 33 = 7

    Ответ: ёмкость ≥ 7 дополнительных слотов сверх текущей загрузки. С запасом выбирают C ≥ 8–10

Упражнение 2. «Дебаунс по K отсчётам: логика и таблица истинности»

Условие. Кнопка даёт шумный битовый поток b(t). Стабильное состояние S меняется только если подряд пришли K=3 одинаковых значений (1 → «нажато», 0 → «отпущено»).

  1. Запишите правило обновления с регистром истории R из 3 бит.
  2. Определите, при каких R и текущем S произойдёт смена состояния.

Решение.


  1. R := ((R << 1) | b) & 0b111

    if S = 0 and R = 0b111 then S := 1

    if S = 1 and R = 0b000 then S := 0

  2. Смена 0→1 при R = 111, смена 1→0 при R = 000; в иных случаях S сохраняется. Это иллюстрация конечного автомата и таблиц истинности (темы ЕГЭ по логике).    

Упражнение 3. «ДКА для ввода PIN: три звезды и решётка»

Условие. Ввод с клавиатуры приходит как события из множества A = {*, #, d}, где d – цифра. Строка считается принятой, если пользователь ввёл ровно три цифры, затем #. Любое другое событие сбрасывает ввод в начальное состояние. Построить таблицу переходов.

Решение.
Состояния: S0 – начало, S1 – введена 1 цифра, S2 – 2 цифры, S3 – 3 цифры, F – принято.
Переходы:

S0 --d--> S1; S0 --*,#--> S0

S1 --d--> S2; S1 --*,#--> S0

S2 --d--> S3; S2 --*,#--> S0

S3 --#--> F ; S3 --d, *--> S0

F  --*,#,d--> S0   // после приёма – сброс

Это типовая задача ЕГЭ на построение конечных автоматов и анализ регулярных шаблонов. 

Упражнение 4. «Планировщик: успеем ли к дедлайну?»

Условие. События типа A и B приходят в моменты времени (мс):

A: 0, 10, 40, 70

B: 5, 15, 45

Время обработки T_h = 12 мс; обработчик один, очередь FIFO. Сколько событий будет обработано за интервал [0; 60] мс включительно (момент завершения обработки ≤ 60)?

Решение.
Сформируем общий поток по времени прибытия: 0(A), 5(B), 10(A), 15(B), 40(A), 45(B), 70(A)
Эмуляция:

  • t=0: берём A(0), завершим в t=12.
  • t=5: B(5) в очереди.
  • t=10: A(10) в очереди.
  • t=12: старт B(5), финиш в t=24.
  • t=15: B(15) в очереди.
  • t=24: старт A(10), финиш в t=36.
  • t=36: очередь пуста до t=40.
  • t=40: старт A(40), финиш в t=52.
  • t=45: B(45) в очереди.

  • t=52: старт B(45), финиш в t=64.

До t=60 завершены обработки в моменты 12, 24, 36, 52 – 4 события

Упражнение 5. «Парсинг журнала событий: агрегирование по типам»

Условие. Дана строка журнала L, содержащая записи вида type@timestamp; (без пробелов), например:
"A@10;B@12;A@15;C@20;B@23;A@30;".
Напишите функцию countByType(L), возвращающую словарь частот типов.

Решение (Python):

def countByType(L: str) -> dict[str, int]:

    freq = {}

    for token in filter(None, L.split(';')):

        t, _ = token.split('@')

        freq[t] = freq.get(t, 0) + 1

    return freq 

# Пример:

# countByType("A@10;B@12;A@15;C@20;B@23;A@30;") -> {'A': 3, 'B': 2, 'C': 1}

Это задача ЕГЭ на строки, словари, подсчёт частот.

Практические шаблоны и приёмы

  1. Идемпотентный обработчик (Python, псевдо-бизнес-ключ)

    def handle_payment(state, event):

        if event.id in state['done']:   # защита от повтора

            return state, None

        # ... проверить контракт payload ...

        new_state = dict(state)

        new_state['balance'] += event.payload['amount']

        new_state['done'].add(event.id)

        return new_state, {'type':'LOG', 'payload':{'msg':'ok', 'id':event.id}}

  2. Троттлинг (JS)

    function throttle(fn, wait) {

      let last = 0;

      return function(...args) {

        const now = Date.now();

        if (now - last >= wait) {

          last = now;

          return fn.apply(this, args);

        }

      };

    }

  3.  Диспетчеризация по типу (таблица переходов)

    handlers = {

      'CLICK': on_click,

      'TICK':  on_tick,

      'DATA':  on_data,

    }

    def dispatch(state, event):

        h = handlers.get(event.type, on_unknown)

        return h(state, event)

Чек-лист разработчика событийных систем 

  • Модель данных события фиксирована: type, payload, t, id.
  • Очередь и планировщик выбраны под нагрузку: FIFO/приоритет/кольцо.
  • Обработчики короткие, неблокирующие, с тайм-аутами и отменой.
  • Состояние меняется атомарно; нет гонок/двойной доставки.
  • Дебаунс/троттлинг настроены под реальный λ.
  • Журналирование и метрики: latency, глубина очереди, процент дедлайнов.
  • Для задач ЕГЭ: умею строить таблицы переходов, считать сложность, работать со строками и очередями.

Контрольные вопросы для самопроверки

  1. Как формально задать событие и почему нам нужна метка времени t и идентификатор id?
  2. В чём различие дебаунса и троттлинга и как их выразить через скользящее окно/таймер?
  3. Запишите условие устойчивости ρ и объясните его смысл.
  4. Почему идемпотентность критична при повторной доставке и восстановлении после ошибки?
  5. Нарисуйте ДКА для шаблона «ровно n символов d, затем #» и укажите переходы. 

Заключение

Событийно-ориентированное программирование – это строго формализуемая практика построения программ как реакций на дискретные стимулы. Формальная модель (S, E, Q, H, δ) объединяет конечные автоматы, очереди и диспетчеризацию, а инженерные правила – чистоту обработчиков, устойчивость по нагрузке, идемпотентность и управляемые побочные эффекты – обеспечивают предсказуемость и надёжность. Для ЕГЭ эта тема служит «мостом» между логикой, автоматами, строками и оценками сложности. Освоив предложенные упражнения, шпаргалку и чек-лист, вы научитесь переводить задачи из экзаменационного формата в работающие событийные решения – и обратно, что резко повышает качество и скорость ваших ответов на экзамене.