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

В программировании термин инициализация используется во множестве разных контекстов. В MQL5, к сожалению, также возникает некоторая неоднозначность. В разделе Инициализация мы уже применяли это слово для обозначения установки начальных значений переменных. Сейчас мы познакомились с событием инициализации OnInit в индикаторах и экспертах. И хотя смысл обоих инициализаций похож (привести программу в рабочее состояние), они на самом деле обозначают разные этапы подготовки MQL-программы к запуску: системный и прикладной.

Жизненный цикл готовой MQL-программы можно представить следующими крупными шагами:

  1. Загрузка — чтение программы из файла в память терминала: сюда входят и инструкции, и предопределенные данные (литералы), и ресурсы, и библиотеки. Здесь же вступают в силу директивы #property.
  2. Выделение памяти под глобальные переменные и установка их начальных значений — это системная инициализация, выполняемая средой исполнения. Напомним, что в разделе Инициализация, изучая старт программы под отладчиком по шагам, мы видели, что в стеке находится запись @global_initializations — это и есть блок кода для данного пункта, который создается неявным образом компилятором. Если в программе используются глобальные объекты классов/структур, на данном этапе будут вызваны их конструкторы.
  3. Вызов обработчика события OnInit (если он есть): в нем проводится более высокоуровневая, прикладная инициализация — каждая программа выполняет её самостоятельно, по необходимости. Например, это может быть динамическое распределение памяти под массивы объектов, для которых по тем или иным причинам нужно использовать параметрические конструкторы вместо конструкторов по умолчанию. Как нам известно, автоматическое распределение памяти под массивы использует только конструкторы по умолчанию, и потому их невозможно проинициализировать в рамках предыдущего шага (2). Также это может быть открытие файлов, вызов встроенных функций API для включения необходимых режимов графика и т.д.
  4. В цикле, пока пользователь не закроет программу или терминал, или не выполнит одно из других действий, требующих переинициализации (см. далее):
    • вызов прочих обработчиков по мере наступления соответствующих событий.
  5. Вызов обработчика события OnDeinit (если он есть) при обнаружении попытки закрыть программу со стороны пользователя или программно (соответствующая функция ExpertRemove доступна только в экспертах и скриптах).
  6. Финализация: освобождение выделенной памяти и прочих ресурсов, которые программист не посчитал нужным освободить в OnDeinit. Если в программе используется ООП, здесь вызываются деструкторы глобальных и статических объектов.
  7. Выгрузка программы.

Скрипты и сервисы априори не имеют обработчиков OnInit и OnDeinit, и потому для них шаги 3 и 5 отсутствуют, а шаг 4 вырождается в единственный вызов OnStart.

Системная инициализация (под пунктом 2) неотделима от загрузки, то есть всегда следует после неё. А финализация всегда предшествует выгрузке. Однако индикаторы и эксперты по-разному проходят этапы загрузки и выгрузки в различных ситуациях. Поэтому вызовы OnInit и OnDeinit (пункты 3 и 5) являются теми опорными точками, в которых можно обеспечить согласованную прикладную инициализацию и деинициализацию экспертов и индикаторов.

Загрузка индикаторов и экспертов происходит по следующим причинам:

Причина

Индикатор

Эксперт

Прикрепление к графику пользователем

+

+

Запуск терминала (если программа была прикреплена к графику перед предыдущим закрытием терминала)

+

+

Загрузка шаблона (если в шаблоне указана прикрепленная к графику программа)

+

+

Смена профиля (если программа прикреплена к одному из графиков профиля)

+

+

После удачной перекомпиляции, если программа была прикреплена к графику

+

+

Смена активного счета

+

+

-

-

-

Смена символа или периода графика, к которому прикреплен индикатор

+

-

Изменение входных параметров индикатора

+

-

-

-

-

Подключение к счету (авторизация), даже если номер счета не менялся

-

+

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

Поэтому аналогичную несимметричность можно наблюдать и при выгрузке программ. Причинами выгрузки для индикаторов и экспертов являются:

Причина

Индикатор

Эксперт

Удаление с графика

+

+

Закрытие терминала (когда программа прикреплена к графику)

+

+

Загрузка шаблона на график, к которому прикреплена программа

+

+

Закрытие графика, к которому прикреплена программа

+

+

Смена профиля, если программа прикреплена к одному из графиков сменяемого профиля

+

+

Смена счета, к которому подключен терминал

+

+

-

-

-

Смена символа и/или периода графика, к которому прикреплен индикатор

+

-

Изменение входных параметров индикатора

+

-

-

-

-

Прикрепление другого или того же эксперта к графику, на котором уже работает текущий эксперт

-

+

Вызов функции ExpertRemove

-

+

Причину деинициализации можно узнать из программы с помощью функции UninitializeReason или флага _UninitReason (см. раздел Проверка статуса и причины остановки MQL-программы).

Обратим еще раз внимание: при смене символа или таймфрейма графика, а также при смене входных параметров эксперт остается в памяти, то есть пункты 6-7 (финализация и выгрузка) и пункты 1-2 (загрузка и первичное распределение памяти) не выполняются, поэтому значения глобальных и статических переменных не сбрасываются. При этом последовательно вызываются обработчики OnDeinit и OnInit на старом и на новом символе/таймфрейме соответственно (или при старых и новых настройках).

Следствием того, что глобальные переменные не очищаются в экспертах, является то, что код деинициализации _UninitReason остается в неизменном виде для анализа в обработчике OnInit. Новый код будет записан в переменную только в случае следующего события, непосредственно перед вызовом OnDeinit.

Все события, поступившие для эксперта до завершения функции OnInit, пропускаются.

При первом запуске MQL-программы диалог настроек выводится между шагами 1 и 2. При смене входных параметров диалог настройки по-разному вклинивается в общий цикл в зависимости от типа программы: для индикаторов он по-прежнему появляется перед шагом 2, а для экспертов — перед шагом 3.
 
К книге прилагается шаблон индикатора и эксперта с одним и тем же названием LifeCycle.mq5. В нем реализован вывод в журнал этапов глобальной инициализации/финализации и в обработчиках OnInit/OnDeinit. Разместите программы на графике и посмотрите, какие события происходят в ответ на различные действия пользователя: загрузку/выгрузку, смену параметров, переключение символа/таймфрейма.

Загрузка скрипта происходит только при набрасывании его на график. Если скрипт выполняется в цикле, его перекомпиляция не приводит к перезапуску.

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

В следующих двух разделах мы рассмотрим особенности запуска разных MQL-программ на уровне обработчиков соответствующих событий.