马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
PowerBuilder Timer 定时器全解:帮助口径、编译期真边界与无窗口环境的 -1 实测(PB10 基准 · PB12.5 实测)
阅读说明
- 适用版本:PB 10 基准,PB 12.5 实测复核;PB 11/2019+ 未逐项验证,但编译口径差异不大。
- 支持数据库:本文不涉及数据库。
- 操作系统与环境:Windows(Timer 事件最终落在 Win32 的 WM_TIMER 机制上);纯 PowerScript,无需任何运行库。
- 难度系数:★★☆☆☆
- 前置知识:会用窗口的 Open/Close 事件即可。
一、Timer 是什么,一句话版本
Timer() 是 PowerScript 的全局函数,作用是让某个窗口的 Timer 事件每隔指定秒数自动触发一次。语法就一行:
- Timer ( interval {, windowname } )
复制代码
interval 是秒数,可以带小数;windowname 可选,不给就用"当前窗口"。返回 Integer:成功 1,出错 -1,任何参数为 null 则返回 null。
听起来简单得没什么可写的。但我在给论坛的自动化测试链路做探针时发现,这个函数的编译期边界和网上一些说法并不一致,而且"目标窗口必须是打开的"这句话在运行期的表现很有意思,值得单独讲一期。
二、先看帮助文档怎么说(PB10 与 PB12.5 口径一致)
我手头两个版本的帮助原文口径完全一致,逐条列出来:
- interval 取值:大于 0 且小于等于 4,294,967 的整数或小数;interval 为 0 时是关表,Timer 事件不再触发。
- windowname:必须是已经打开的窗口。
- 不指定 windowname 时,事件发生在"当前窗口"。
- 调用 Timer 时启动计时,interval 到了就触发一次 Timer 事件,然后计时器自动重置,周而复始。
其中最容易踩的两个点:一是 0 不是"最小间隔"而是"关表",想停定时器只能传 0;二是"必须打开的窗口"这个前提,很多人随手把一个只是声明了的窗口变量传进去,运行期就悄悄返回 -1 了。下面这两条我都是实机测出来的。
三、编译期真边界:目标只能是窗口,两版编译器都不含糊
网上偶有说法称"Timer 的目标也可以是 user object"。我在 PB12.5 和 PB10 下分别做了编译探测,结论很干脆:不行。
探针逻辑很简单:在一个 NVO 的方法里写 Timer(0.5, ln),其中 ln 是一个 nonvisualobject 实例。编译结果:
- PB 12.5:C0052 — Bad argument list for function: timer
- PB 10:C0052,逐字相同
我又换了一个带 event timer pbm_timer 的可视 user object 试了一次,两版同样 C0052。所以至少在 PB10 和 PB12.5,这个函数的第二个参数在编译器层面只认窗口类型。你要是看到哪个资料说 user object 也能收 Timer,先想想是不是新版本(2019+)的行为,老版本别照抄。
反过来,传一个窗口类型的变量,哪怕它还没打开,编译器也不拦——这就是下一节的坑。
四、运行期实测:目标不是"打开的窗口"时,-1 来得很安静
这一节的数字全部来自本轮探针的实机运行,两版读数一致:
场景一:窗口实例只有 create、没有 open。
- w_t88h lw
- long ll_r
- lw = create w_t88h
- ll_r = Timer(0.3, lw)
- // ll_r = -1(PB10 与 PB12.5 实测一致)
复制代码
窗口实例活生生存在,类型完全正确,编译零错误,但 Timer 返回 -1。原因就是帮助里那句话:目标必须是打开的窗口。create 只是让对象存在,Open 才让它成为屏幕意义上的窗口。这个 -1 不会抛错、不会弹框,如果你不检查返回值,程序就继续跑,定时器根本没启动,然后你会花一下午怀疑人生。
场景二:不指定目标,而且当前没有窗口。
- long ll_r
- ll_r = Timer(0.3)
- // ll_r = -1(两版一致)
复制代码
帮助说"不指定就用当前窗口",那么在没有任何窗口的上下文里调用(比如纯后台批处理场景),它不会崩,也是安静地 -1。这条对写服务类程序的人特别有用:在无窗口的上下文里直接调 Timer() 是永远起不来的,先有个打开的窗口再说。
顺带说一句,Timer(0, 窗口) 在"本来就没开表"的窗口上调用,返回的也是 -1——关一个不存在的表也算出错。想安全停表,别管有没有开过,直接调就行,但要明白 -1 在这里不代表"没停成功",而是"本来就没有表可停"。
五、间隔与精度:这部分我没有实测数据,先把机制讲清楚
要诚实交代一句:本节内容未实测、仅静态核对。我的自动化测试链路跑在无界面的 ORCA 编译环境里,窗口打不开(open 直接返回 -1),含窗口 Timer 事件的应用重建又被工具链卡住,所以 interval 的实际触发间隔、事件在长任务下的堆积行为,我没有拿到本机实测数字。以下按官方文档口径加 Win32 机制推演,读者在桌面环境照第五节的模板自己跑一遍即可验证。
按文档机制:
- interval 到点 → 触发 Timer 事件 → 计时器自动重置,再等下一个 interval。所以理论上事件间隔就是 interval 设定值。
- 底层是 Win32 的 WM_TIMER:这是个"合并型"消息——如果你的 Timer 事件体里跑了一个 300 毫秒的长任务,而 interval 只有 50 毫秒,积压的 WM_TIMER 不会排队排成 6 个补发,而是合并。事件体越长,实际触发频率被拉得越低。想在事件体里做"每 50 毫秒一次"的密集活,这个机制决定了它天生不精确。
- Timer 事件是贴在消息队列上的。如果你的代码在一段死循环里忙(不调 Yield 也不回消息泵),Timer 事件一根毛都不会触发,等循环结束回到消息泵,积压的合并事件才来一次。做进度条刷新的人常踩这个:循环里不 Yield,界面和定时器一起冻住。
- 小数 interval 是支持的,比如 0.1 就是 100 毫秒。但受 WM_TIMER 的系统精度限制(一般 10~16 毫秒的粒度),别指望 0.001 这种值能真按 1 毫秒触发。
六、正确用法模板(未实测、仅静态核对,桌面环境可直接照抄验证)
- // 窗口 w_demo,带 Timer 事件(事件 ID 选 pbm_timer)
- // 实例变量:long il_cnt
- // Open 事件:启动定时器,500 毫秒一次
- event open;
- Timer(0.5, this)
- end event
- // Timer 事件:每次触发做的事,尽量短
- event timer;
- il_cnt = il_cnt + 1
- st_1.text = String(il_cnt)
- end event
- // Close 事件:收尾停表(窗口关了表也会随之失效,但显式停是好习惯)
- event close;
- Timer(0, this)
- end event
复制代码
三个要点:目标给 this 就够了(窗口自己的脚本里当前窗口就是 this);事件体尽量短,长任务挪出去或者加防重入标志;Close 里补一个 Timer(0, this),防御性收尾。
七、常见坑清单
- 不检查返回值:-1 是 Timer 出错的唯一信号,而且错得悄无声息。启动定时器的调用建议一律接返回值。
- 把 0 当最小间隔:0 是关表。想要最快触发,给 0.01 之类的小数,而不是 0。
- create 之后就 Timer:没 Open 的窗口收不到表,返回 -1(本文实测)。
- 事件体里塞长任务:WM_TIMER 合并机制会让触发频率缩水,重活用防重入标志或挪到别的对象。
- 循环里不 Yield:整个消息泵停摆,Timer 事件被冻住,循环结束才补一次。
- 以为 user object 能当目标:PB10/PB12.5 编译器直接 C0052,实测不认。
八、PB10 与 PB12.5 差异
本轮实测下来,Timer 在两版上编译级与可测行为完全一致:目标给 NVO 或可视 user object 都是 C0052;未打开窗口、无当前窗口都是返回 -1。帮助文档口径两版也逐字一致。运行级的间隔精度部分我没有实测数据,按机制推断两版不会有差异,如你手头有两版桌面环境且测出差异,欢迎到论坛回帖告诉我。
九、实机验证情况
- 本文第三节、第四节的全部结论为实机实测:探针经 ORCA 编译 0 错误、pbtest 运行 PASS,PB10 与 PB12.5 双轨读数逐字一致;check_pb125.py 0 错 0 提醒,check_enc.py 编码全 OK。
- 第五、六节标注「未实测、仅静态核对」,原因是本机自动化编译链路无法创建真实窗口(这是测试环境的边界,不是 Timer 本身的问题),代码模板请在真实桌面环境验证后再用于生产。
有别的用法或翻车姿势,评论区见。 |