很多同学在写易语言多线程的demo中,喜欢给全局变量加个锁(临界许可),甚至还把线程也放到了临界区内,对于窗口子类化,已经窗口的异步消息又嫌麻烦,那么下面的建议,值得你学习。

结论先行

只加临界区不能从根本上解决问题,只能缓解多子线程之间的竞争,依然存在界面卡死、随机崩溃、资源泄漏的隐患

Windows 窗口控件有严格的线程亲和性:所有UI控件只能在创建它的主线程中操作,这是Windows底层的窗口消息机制决定的,不是加锁能解决的。临界区只能保护内存数据,保护不了窗口消息的执行上下文。


一、为什么你现在的写法有隐患

  1. 消息死锁风险 超级列表框的置标题方法内部本质是调用SendMessage,这是同步调用,会等待主线程的窗口过程返回。如果此时主线程正在等待子线程退出(比如点停止时),子线程又卡在SendMessage等主线程响应,就会形成死锁,程序直接无响应。

  2. 控件内部状态错乱 易语言超级列表框内部有自己的缓存和状态管理,跨线程调用方法会破坏其内部数据结构,出现偶发的乱码、行错位、资源泄漏,运行几小时后无提示退出,这类问题极难排查。

  3. 临界区的局限性 临界区只能防止「两个子线程同时操作控件」,但防不了「子线程操作控件 + 主线程重绘控件」的冲突,这才是跨线程UI最核心的问题。


二、推荐方案:消息队列 + 主线程定时器批量更新

这是易语言多线程UI更新的工业级标准方案,完全复用你已经熟悉的「队列+临界区」模式,不用子类化、不用消息钩子,实现简单且绝对稳定。

核心思路

  • 子线程只负责生产UI更新请求,把要更新的索引、列、内容丢进队列,立刻返回,不碰控件
  • 主线程用定时器(20~50ms)从队列取出所有请求,批量更新超级列表框
  • 所有UI操作全部回到主线程执行,完全符合Windows窗口规则

完整实现代码

1. 新增自定义数据类型 + 全局队列

.版本 2

' UI更新消息结构体
.数据类型 UI消息
    .成员 表项索引, 整数型
    .成员 列索引, 整数型
    .成员 内容, 文本型

' 全局UI消息队列(用你已有的精易类_队列即可,自带线程安全)
.全局变量 UI消息队列, 类_队列

2. 改造「信息反馈」子程序(子线程调用)

不再直接操作控件,只把消息压入队列,瞬间完成,不阻塞线程。

.版本 2
.支持库 iext

.子程序 信息反馈
.参数 表项索引, 整数型
.参数 列索引, 整数型
.参数 标题内容, 文本型
.局部变量 消息, UI消息
.局部变量 消息指针, 整数型

' 封装消息
消息.表项索引 = 表项索引
消息.列索引 = 列索引
消息.内容 = 标题内容

' 分配内存并写入消息(和你任务队列的逻辑完全一致)
消息指针 = 申请内存 (取数据类型大小 (UI消息), 假)
写到内存 (消息, 消息指针, )

' 压入线程安全队列
UI消息队列.压入整数 (消息指针)

3. 主线程添加定时器刷新UI

在启动窗口放一个定时器组件,周期设为20ms(人眼无感知延迟),在周期事件里批量处理所有更新。

.版本 2
.支持库 iext

.子程序 _定时器_UI刷新_周期事件
.局部变量 消息指针, 整数型
.局部变量 消息, UI消息

' 循环取出所有待更新消息,一次处理完
.判断循环首 (UI消息队列.弹出整数 (消息指针))
    ' 读取消息内容
    读入内存 (消息指针, 消息, )

    ' 主线程操作控件,绝对安全
    超级列表框_任务.置标题 (消息.表项索引, 消息.列索引, 消息.内容)

    ' 释放内存,避免泄漏
    释放内存 (消息指针)
.判断循环尾 ()

三、方案优势

  1. 绝对线程安全:所有控件操作都在主线程执行,从根源上规避Windows窗口线程亲和性问题,不会再出现随机崩溃、卡死。
  2. 实现成本极低:复用你已经熟练的「队列+指针+内存管理」模式,和任务队列逻辑完全一致,没有新的学习成本。
  3. 性能更优:批量更新比每次单独调用置标题效率高很多,子线程也不会因为等待UI响应而阻塞。
  4. 可扩展性强:后续要加状态变色、插入行、删除行等其他UI操作,只需要扩展UI消息结构体即可,架构不用动。

四、补充说明

关于更新延迟

20ms的刷新周期,人眼完全感知不到延迟。如果你的更新频率特别低,也可以调到50ms,进一步降低CPU占用。

关于队列自带的锁

精易的类_队列内部已经封装了临界区,入队出队都是线程安全的,所以UI消息队列不需要你额外再加临界许可。

停止时的清理

停止按钮的最后,加上清空UI消息队列的代码,避免未处理的消息残留:

UI消息队列.清空 ()

临时应急方案(不推荐)

如果实在不想改结构,非要在子线程里直接操作,可以把置标题换成用PostMessage异步发送消息,比直接调用方法安全一点,但依然属于违规操作,只适合临时测试。长期稳定运行,一定用「队列+定时器」的方案。