Qt 线程、取消与关闭协议
1. QThread 的两个对象
GUI thread owns QThread QObject
|
+-- start() -> OS worker thread runs QThread::run
|
+-- default run enters event loopQThread 对象本身通常属于创建它的线程;它的 slots 被普通/queued 调用时按对象 affinity 执行,不自动在 run() 线程。把工作放到独立 worker QObject 并 moveToThread 通常更清晰。
2. Worker-object
worker 无 parent,移动到 thread,thread started 触发 worker 开始,worker finished 触发 thread quit。所有 owner/连接/删除顺序集中在 controller。
普通直接调用 worker 方法仍在调用方线程,必须用 signal 或 QMetaObject::invokeMethod queued 调度。不要通过 mutex 让 QWidget 在 worker 中可用;GUI 只在 GUI 线程。
3. 取消不是 queued stop 的保证
若 worker 正在一个长循环/阻塞调用里且不返回事件循环,排队的 stop() slot 无法执行。方案:
- 原子取消标志/stop token 在小工作块间检查;
- 阻塞 API 使用原生取消、超时或关闭 handle;
- 将计算拆为事件驱动步骤;
- 外部求解器用 QProcess 协议取消 + deadline/kill。
4. 进度背压
高频 queued progress 会塞满 GUI 事件队列。把进度存在原子/受锁最新值,用 QTimer 定期拉取,或 worker 限频;完成/错误单独可靠传递。
worker produces 100k updates/s
GUI paints 60 frames/s
=> 必须合并/丢弃过期进度,而非无限排队5. 数据交接
跨线程传不可变快照、值对象或 shared immutable buffer。传裸指针/QObject 指针只复制地址。大型网格可共享只读 storage + generation ID,GUI 收到后验证文档仍是同一版本。
不要 worker 写数据、GUI 无锁读,即使“只有进度信号同步”;同步关系必须覆盖具体数据。
6. 关闭状态机
Running -> StopRequested -> Finishing -> ThreadStopped -> Destroyed
| |
wake blocking emit final result once窗口关闭/文档关闭时:停止接收任务,发取消,唤醒阻塞,等待 worker/thread,处理超时策略,最后销毁共享数据和 UI。不能先删文档/worker 再让线程继续引用。
QThread::terminate() 可在任意点杀线程,可能遗留锁和资源不变量,通常只作为进程即将强退的最后手段,不是正常取消。
7. QFuture/QtConcurrent
QtConcurrent 适合可分割计算,但取消支持取决于具体操作和用户函数是否协作。QFutureWatcher 属于某线程,通过信号通知;watcher 销毁不一定停止底层工作。明确 result ownership、异常/错误模型和 pool 过载。
8. 面试速答
moveToThread 后直接调用方法在哪执行? 调用线程;只有 queued event/connection 才切到 affinity 线程。
为什么 stop slot 可能永远不执行? worker 长时间占住线程且不返回事件循环。
关闭时为什么要 wait? 证明线程不再访问即将销毁的 QObject、数据和同步设施。
9. 自测答案
worker 是否可直接 emit 含 QImage 的信号?值可排队但评估隐式共享/分离和大小;QPixmap 等 GUI 资源有更严格 GUI 线程边界。窗口析构能否只 deleteLater worker?不能,必须先建立停止和线程完成协议。
任务关闭比启动更重要
Idle -> Running -> StopRequested -> Finishing -> Finished
| error -> Failed每个 worker/task 都要定义停止请求、检查点、在途 I/O、结果交付与最终资源回收。强杀线程会跳过 C++/Qt 清理并破坏锁/文件状态,不能作为普通取消。
QObject worker 模式
GUI owns QThread controller
Worker(no parent) moved to thread
thread.started -> Worker::run
Worker progress/result -> GUI queued slots
Worker finished -> thread.quit
thread.finished -> Worker::deleteLater
GUI shutdown -> requestStop -> wait创建/移动应在任务开始前完成;worker slot 在亲和线程事件循环执行,若一个长 slot 永不返回,其他 queued stop 信号也无法被处理,所以任务必须分块或用线程安全停止标志。
QThreadPool/QtConcurrent 的所有权
池适合短期独立任务,但捕获引用仍可能悬空。future watcher 属于 GUI 时可接收完成通知;关闭文档时取消只是请求,仍需等待或忽略带旧 generation 的结果。
Stop flag 的同步
跨线程标志用 std::atomic_bool、QAtomic... 或锁保护,普通 bool 并发读写是 data race。标志只表达取消请求;任务对其他数据的发布仍需 queued connection、mutex/future 等关系。
可取消阻塞
long CPU loop -> bounded chunks + check stop
condition wait -> predicate includes stop + notify
network I/O -> timeout/cancel socket/event loop API
external process -> graceful terminate + deadline + kill fallback若阻塞调用没有取消接口,将其放线程并不让关闭安全;至少用 deadline 并让上层知道最坏等待时间。
进度与结果节流
worker 每处理一个元素发 progress 会淹没 GUI event queue。按时间/百分比节流,合并批次;GUI 接到 progress 仍要核对 task ID,旧任务消息不能覆盖新任务 UI。
关闭顺序
disable new UI commands
-> mark document/task generation closing
-> request cancellation
-> wake blocking waits / cancel I/O
-> wait threads/futures
-> disconnect/destroy worker
-> destroy document/model/render resources不要在 GUI 主线程无限 wait;可以显示关闭进度、泵受控异步状态,但避免任意 processEvents() 造成重入。
异常边界
C++ 异常不能逃出线程入口/Qt event callback。worker 捕获并转换为结构化 error signal/result;析构与 Qt 槽边界保持 noexcept 语义,GUI 决定展示、重试或关闭。
连续追问与自测
问:发 queued stop signal 一定能停止长 slot 吗? 答:不一定,长 slot 不返回时事件循环无法处理该信号,应使用原子 stop/分块任务或可取消 API。
问:disconnect 是否取消 worker? 答:不取消,只停止信号路由,worker 仍可能运行并访问状态。
- 普通 bool 可做跨线程取消吗? 答:不可,无同步读写是 data race。
- 为何 wait 在销毁 document 前? 答:确保 worker 不再引用其数据/model/render 资源。
- 旧结果如何隔离? 答:结果携带 task/document generation,提交时验证。
工程深化:把本篇知识落到项目里
这一节不是为了凑篇幅,而是把《Qt 线程、取消与关闭协议》从“知道概念”推进到“能在项目里稳定使用”。 阅读时可以把每个知识点都追问成四件事:它保护什么边界,失败时有什么现象,怎样最小复现,怎样写进团队流程。
concept -> boundary -> failure signal -> minimal proof -> project rule工程切片 1:最小可复现样例
**场景。**围绕 worker object 建一个很小的案例,不要一开始就放进完整业务系统。把问题压缩到一个可以提交给同事的目录,保留源码、构建命令、版本输出和预期现象。
**边界。**先判断这里讨论的是 worker object、queued connection 还是 取消 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> worker object -> queued connection -> 取消 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 2:接口边界
**场景。**围绕 queued connection 建一个很小的案例,不要一开始就放进完整业务系统。写清调用方能依赖什么、不能依赖什么,并把隐式假设转换成命名函数、配置项或测试。
**边界。**先判断这里讨论的是 queued connection、取消 还是 关闭 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> queued connection -> 取消 -> 关闭 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 3:失败注入
**场景。**围绕 取消 建一个很小的案例,不要一开始就放进完整业务系统。故意制造一个常见错误,让日志、断言或测试先失败,再用修复后的证据说明规则生效。
**边界。**先判断这里讨论的是 取消、关闭 还是 GUI 线程 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> 取消 -> 关闭 -> GUI 线程 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 4:跨平台差异
**场景。**围绕 关闭 建一个很小的案例,不要一开始就放进完整业务系统。至少比较 Windows、Linux 或不同编译器下的一个差异,记录差异属于标准、实现还是平台约定。
**边界。**先判断这里讨论的是 关闭、GUI 线程 还是 任务归属 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> 关闭 -> GUI 线程 -> 任务归属 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 5:性能观察
**场景。**围绕 GUI 线程 建一个很小的案例,不要一开始就放进完整业务系统。用小规模和压力规模各跑一次,区分算法成本、同步成本、I/O 成本和工具链配置成本。
**边界。**先判断这里讨论的是 GUI 线程、任务归属 还是 worker object 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> GUI 线程 -> 任务归属 -> worker object -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 6:生命周期
**场景。**围绕 任务归属 建一个很小的案例,不要一开始就放进完整业务系统。标出资源创建、转移、共享、停止和销毁的顺序,特别关注错误返回和提前退出路径。
**边界。**先判断这里讨论的是 任务归属、worker object 还是 queued connection 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> 任务归属 -> worker object -> queued connection -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 7:诊断证据
**场景。**围绕 worker object 建一个很小的案例,不要一开始就放进完整业务系统。保留命令、日志、符号、栈、测试输出或截图,让结论可以被另一个环境重新验证。
**边界。**先判断这里讨论的是 worker object、queued connection 还是 取消 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> worker object -> queued connection -> 取消 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 8:维护策略
**场景。**围绕 queued connection 建一个很小的案例,不要一开始就放进完整业务系统。把一次性经验沉淀到 README、CI、脚本、示例工程或检查表里,避免只存在个人记忆中。
**边界。**先判断这里讨论的是 queued connection、取消 还是 关闭 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> queued connection -> 取消 -> 关闭 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 9:版本演进
**场景。**围绕 取消 建一个很小的案例,不要一开始就放进完整业务系统。为未来变更预留兼容策略:版本号、特性位、弃用窗口、迁移脚本和回滚入口。
**边界。**先判断这里讨论的是 取消、关闭 还是 GUI 线程 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> 取消 -> 关闭 -> GUI 线程 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 10:最小消费者
**场景。**围绕 关闭 建一个很小的案例,不要一开始就放进完整业务系统。从使用者角度写一个小程序或小工程,只依赖公开接口,验证安装包、头文件和运行时行为。
**边界。**先判断这里讨论的是 关闭、GUI 线程 还是 任务归属 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> 关闭 -> GUI 线程 -> 任务归属 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 11:边界复盘
**场景。**围绕 GUI 线程 建一个很小的案例,不要一开始就放进完整业务系统。用一张表说明问题发生在源码、编译、链接、加载、运行、关闭或发布中的哪一层。
**边界。**先判断这里讨论的是 GUI 线程、任务归属 还是 worker object 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> GUI 线程 -> 任务归属 -> worker object -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 12:反例训练
**场景。**围绕 任务归属 建一个很小的案例,不要一开始就放进完整业务系统。写出一个看似能工作但不可维护的方案,再说明它在规模、团队协作或发布时会怎样失败。
**边界。**先判断这里讨论的是 任务归属、worker object 还是 queued connection 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> 任务归属 -> worker object -> queued connection -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 13:最小可复现样例
**场景。**围绕 worker object 建一个很小的案例,不要一开始就放进完整业务系统。把问题压缩到一个可以提交给同事的目录,保留源码、构建命令、版本输出和预期现象。
**边界。**先判断这里讨论的是 worker object、queued connection 还是 取消 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> worker object -> queued connection -> 取消 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 14:接口边界
**场景。**围绕 queued connection 建一个很小的案例,不要一开始就放进完整业务系统。写清调用方能依赖什么、不能依赖什么,并把隐式假设转换成命名函数、配置项或测试。
**边界。**先判断这里讨论的是 queued connection、取消 还是 关闭 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> queued connection -> 取消 -> 关闭 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 15:失败注入
**场景。**围绕 取消 建一个很小的案例,不要一开始就放进完整业务系统。故意制造一个常见错误,让日志、断言或测试先失败,再用修复后的证据说明规则生效。
**边界。**先判断这里讨论的是 取消、关闭 还是 GUI 线程 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> 取消 -> 关闭 -> GUI 线程 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 16:跨平台差异
**场景。**围绕 关闭 建一个很小的案例,不要一开始就放进完整业务系统。至少比较 Windows、Linux 或不同编译器下的一个差异,记录差异属于标准、实现还是平台约定。
**边界。**先判断这里讨论的是 关闭、GUI 线程 还是 任务归属 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> 关闭 -> GUI 线程 -> 任务归属 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 17:性能观察
**场景。**围绕 GUI 线程 建一个很小的案例,不要一开始就放进完整业务系统。用小规模和压力规模各跑一次,区分算法成本、同步成本、I/O 成本和工具链配置成本。
**边界。**先判断这里讨论的是 GUI 线程、任务归属 还是 worker object 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> GUI 线程 -> 任务归属 -> worker object -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 18:生命周期
**场景。**围绕 任务归属 建一个很小的案例,不要一开始就放进完整业务系统。标出资源创建、转移、共享、停止和销毁的顺序,特别关注错误返回和提前退出路径。
**边界。**先判断这里讨论的是 任务归属、worker object 还是 queued connection 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> 任务归属 -> worker object -> queued connection -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 19:诊断证据
**场景。**围绕 worker object 建一个很小的案例,不要一开始就放进完整业务系统。保留命令、日志、符号、栈、测试输出或截图,让结论可以被另一个环境重新验证。
**边界。**先判断这里讨论的是 worker object、queued connection 还是 取消 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> worker object -> queued connection -> 取消 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 20:维护策略
**场景。**围绕 queued connection 建一个很小的案例,不要一开始就放进完整业务系统。把一次性经验沉淀到 README、CI、脚本、示例工程或检查表里,避免只存在个人记忆中。
**边界。**先判断这里讨论的是 queued connection、取消 还是 关闭 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> queued connection -> 取消 -> 关闭 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 21:版本演进
**场景。**围绕 取消 建一个很小的案例,不要一开始就放进完整业务系统。为未来变更预留兼容策略:版本号、特性位、弃用窗口、迁移脚本和回滚入口。
**边界。**先判断这里讨论的是 取消、关闭 还是 GUI 线程 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> 取消 -> 关闭 -> GUI 线程 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 22:最小消费者
**场景。**围绕 关闭 建一个很小的案例,不要一开始就放进完整业务系统。从使用者角度写一个小程序或小工程,只依赖公开接口,验证安装包、头文件和运行时行为。
**边界。**先判断这里讨论的是 关闭、GUI 线程 还是 任务归属 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> 关闭 -> GUI 线程 -> 任务归属 -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 23:边界复盘
**场景。**围绕 GUI 线程 建一个很小的案例,不要一开始就放进完整业务系统。用一张表说明问题发生在源码、编译、链接、加载、运行、关闭或发布中的哪一层。
**边界。**先判断这里讨论的是 GUI 线程、任务归属 还是 worker object 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> GUI 线程 -> 任务归属 -> worker object -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
工程切片 24:反例训练
**场景。**围绕 任务归属 建一个很小的案例,不要一开始就放进完整业务系统。写出一个看似能工作但不可维护的方案,再说明它在规模、团队协作或发布时会怎样失败。
**边界。**先判断这里讨论的是 任务归属、worker object 还是 queued connection 的责任;如果三者混在一起,先把输入、输出和所有权拆开。
input -> 任务归属 -> worker object -> queued connection -> observable result**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。
**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。
**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?
本篇收束
掌握《Qt 线程、取消与关闭协议》的标志,不是记住所有名词,而是能把 worker object、queued connection、取消、关闭、GUI 线程、任务归属 放进一条可验证的工程链路。 先用最小样例建立判断,再用测试和脚本固定判断,最后把失败证据留给未来的自己和团队。