各类程序启动和停止的特征
在编程领域,“初始化”这一术语被用于多种不同上下文。在 MQL5 中,该术语同样具有多重性。在 初始化 章节中,我们曾用该术语表示设置变量的初始值。随后,我们讨论了指标和 EA 交易中的初始化事件OnInit。尽管这两种初始化的含义相近(均使程序进入可工作状态),但实际上它们代表了 MQL 程序启动准备的两个不同准备阶段:系统初始化和应用初始化。
一个完整的 MQL 程序的生命周期可以概括为以下主要阶段:
- 加载 - 将程序从文件读取到终端内存中:包括指令、预定义数据(字面量)、 资源和 库。正是在这一阶段,#property指令发挥作用。
- 为全局变量分配内存并设置其初始值 - 这是由运行时执行的系统初始化。回顾 初始化章节内容,当我们通过调试器逐步分析程序启动过程时,我们看到 @global_initializations 条目出现在堆栈中。这是针对该项目的代码块,由编译器隐式创建。如果程序使用类/结构体的全局对象,则其 构造函数 将在该阶段进行调用。
- 调用OnInit事件处理程序(如果存在):它将执行更高级别的应用层面初始化操作,因此每个程序会根据需要独立执行此操作。例如,可能需要为对象数组动态分配内存,出于某种原因,需要使用参数化构造函数,而不是默认构造函数。如我们所知,数组的自动内存分配仅使用默认构造函数,因此无法在前面的步骤 (2) 中完成初始化。还可能包括打开文件、调用内置 API 函数以启用必要的图表模式等操作。
- 保持循环,直至用户关闭程序/终端,或执行需要重新初始化的任何其他操作(详见后文):
- 根据发生的事件调用相应的处理程序。
- 当检测到用户或程序尝试关闭程序时(仅 EA 交易和脚本中可使用对应函数 ExpertRemove ),调用 OnDeinit事件处理程序(如果存在)。
- 最终清理:释放程序员未在OnDeinit中处理的已分配内存和其他资源。如果程序使用了 OOP,全局对象和静态对象的析构函数将在该阶段进行调用。
- 下载程序。
脚本和服务默认不具备 OnInit和 OnDeinit 处理程序,因此它们不存在步骤 3 和步骤 5,且步骤 4 简化为单次调用 OnStart。
系统初始化(步骤 2)与加载阶段不可分割,即系统初始化总是紧随加载阶段之后执行。最终清理始终在卸载前执行。然而,指标和 EA 交易在不同场景下的加载和卸载机制存在差异。因此,OnInit和 OnDeinit 调用(步骤 3 和 5)是能够为 EA 交易和指标提供一致的应用初始化和反初始化的参考点。
在以下场景下执行指标和 EA 交易加载:
场景 |
||
---|---|---|
用户在图表上启动程序 |
+ |
+ |
启动终端(如果程序在终端上次关闭前已在图表上运行) |
+ |
+ |
加载模板(如果模板包含已附加到图表的程序) |
+ |
+ |
配置文件变更(如果程序附加到其中一个配置文件图表) |
+ |
+ |
成功重新编译后(如果程序之前已附加到图表) |
+ |
+ |
更改活动账户 |
+ |
+ |
- |
- |
- |
更改指标所附加的图表的交易品种或周期 |
+ |
- |
更改指标的输入参数 |
+ |
- |
- |
- |
- |
连接账户(授权),无论账户号码是否发生变化 |
- |
+ |
可以更简洁的形式制定以下规则:EA 交易不会经历完整的生命周期,即当图表的交易品种/时间范围发生变更时,以及当输入参数变更时,它们不会重新加载。
因此,在卸载程序时可以观察到类似不对称性。卸载指标和 EA 交易的原因如下:
场景 |
||
---|---|---|
从图表中移除程序 |
+ |
+ |
关闭终端(当程序附加到图表时) |
+ |
+ |
在正在运行程序的图表上加载模板 |
+ |
+ |
关闭正在运行程序的图表 |
+ |
+ |
更改配置文件(如果程序已附加到配置文件的其中一个图表) |
+ |
+ |
更改终端所连接到的账户 |
+ |
+ |
- |
- |
- |
更改指标所附加的图表的交易品种和/或周期 |
+ |
- |
更改指标的输入参数 |
+ |
- |
- |
- |
- |
将另一个或同一个 EA 附加到当前 EA 已在运行的图表上 |
- |
+ |
调用 ExpertRemove 函数 |
- |
+ |
反初始化的原因可以在程序中通过函数 UninitializeReason或标志 _UninitReason 来查找(请参阅 检查停止 MQL 程序的状态和原因章节)。
请注意,更改图表的交易品种或时间范围时,以及更改输入参数时,EA 交易将保留在内存中,即不会执行步骤 6 至 7(最终清理和卸载)以及步骤 1 至 2(加载和主内存分配),因此全局变量和静态变量的值不会进行重置。在这种情况下,会分别针对旧的和新的交易品种/时间范围(或者旧的和新的设置)依次调用 OnDeinit和 OnInit 处理程序。
由于 EA 交易中的全局变量不会被清除,因此反初始化代码 _UninitReason在OnInit 处理程序中保持不变,可供分析使用。只有在下一个事件发生时,且在调用 OnDeinit之前,新代码才会被写入变量中。
在 OnInit函数结束之前接收到的针对 EA 交易的所有事件都会被跳过。
当 MQL 程序首次启动时,设置对话框会在步骤 1 和步骤 2 之间显示。当更改输入参数时,设置对话框会根据程序类型以不同方式插入到主循环中:对于指标,对话框仍会在步骤 2 之前显示,对于 EA 交易,则会在步骤 3 之前显示。
本书附带名为LifeCycle.mq5的指标和 EA 交易模板。它会在OnInit/OnDeinit处理程序中记录全局初始化/最终清理步骤。将程序放置在图表上,观察以下各种用户操作会触发哪些事件:加载/卸载、更改参数、切换交易品种/时间范围。
脚本仅在添加到图表时加载一次。如果脚本正在循环运行,重新编译不会导致其重启。
服务通过终端界面上下文菜单命令进行加载和卸载。若某个已在运行的服务被重新编译,该服务将重新启动。请记住,处于活动状态的服务实例将在终端启动时自动加载,并在终端关闭时自动卸载。
在接下来两个章节,我们将从事件处理程序的层面探讨启动不同 MQL 程序的特性。