Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

本页目录

Qt 线程、取消与关闭协议 ​

1. QThread 的两个对象 ​

text
GUI thread owns QThread QObject
                 |
                 +-- start() -> OS worker thread runs QThread::run
                                  |
                                  +-- default run enters event loop
1
2
3
4
5

QThread 对象本身通常属于创建它的线程;它的 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 限频;完成/错误单独可靠传递。

text
worker produces 100k updates/s
GUI paints 60 frames/s
=> 必须合并/丢弃过期进度,而非无限排队
1
2
3

5. 数据交接 ​

跨线程传不可变快照、值对象或 shared immutable buffer。传裸指针/QObject 指针只复制地址。大型网格可共享只读 storage + generation ID,GUI 收到后验证文档仍是同一版本。

不要 worker 写数据、GUI 无锁读,即使“只有进度信号同步”;同步关系必须覆盖具体数据。

6. 关闭状态机 ​

text
Running -> StopRequested -> Finishing -> ThreadStopped -> Destroyed
              |                |
         wake blocking     emit final result once
1
2
3

窗口关闭/文档关闭时:停止接收任务,发取消,唤醒阻塞,等待 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?不能,必须先建立停止和线程完成协议。

任务关闭比启动更重要 ​

text
Idle -> Running -> StopRequested -> Finishing -> Finished
                       | error -> Failed
1
2

每个 worker/task 都要定义停止请求、检查点、在途 I/O、结果交付与最终资源回收。强杀线程会跳过 C++/Qt 清理并破坏锁/文件状态,不能作为普通取消。

QObject worker 模式 ​

text
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
1
2
3
4
5
6
7

创建/移动应在任务开始前完成;worker slot 在亲和线程事件循环执行,若一个长 slot 永不返回,其他 queued stop 信号也无法被处理,所以任务必须分块或用线程安全停止标志。

QThreadPool/QtConcurrent 的所有权 ​

池适合短期独立任务,但捕获引用仍可能悬空。future watcher 属于 GUI 时可接收完成通知;关闭文档时取消只是请求,仍需等待或忽略带旧 generation 的结果。

Stop flag 的同步 ​

跨线程标志用 std::atomic_bool、QAtomic... 或锁保护,普通 bool 并发读写是 data race。标志只表达取消请求;任务对其他数据的发布仍需 queued connection、mutex/future 等关系。

可取消阻塞 ​

text
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
1
2
3
4

若阻塞调用没有取消接口,将其放线程并不让关闭安全;至少用 deadline 并让上层知道最坏等待时间。

进度与结果节流 ​

worker 每处理一个元素发 progress 会淹没 GUI event queue。按时间/百分比节流,合并批次;GUI 接到 progress 仍要核对 task ID,旧任务消息不能覆盖新任务 UI。

关闭顺序 ​

text
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
1
2
3
4
5
6
7

不要在 GUI 主线程无限 wait;可以显示关闭进度、泵受控异步状态,但避免任意 processEvents() 造成重入。

异常边界 ​

C++ 异常不能逃出线程入口/Qt event callback。worker 捕获并转换为结构化 error signal/result;析构与 Qt 槽边界保持 noexcept 语义,GUI 决定展示、重试或关闭。

连续追问与自测 ​

问:发 queued stop signal 一定能停止长 slot 吗? 答:不一定,长 slot 不返回时事件循环无法处理该信号,应使用原子 stop/分块任务或可取消 API。

问:disconnect 是否取消 worker? 答:不取消,只停止信号路由,worker 仍可能运行并访问状态。

  1. 普通 bool 可做跨线程取消吗? 答:不可,无同步读写是 data race。
  2. 为何 wait 在销毁 document 前? 答:确保 worker 不再引用其数据/model/render 资源。
  3. 旧结果如何隔离? 答:结果携带 task/document generation,提交时验证。

工程深化:把本篇知识落到项目里 ​

这一节不是为了凑篇幅,而是把《Qt 线程、取消与关闭协议》从“知道概念”推进到“能在项目里稳定使用”。 阅读时可以把每个知识点都追问成四件事:它保护什么边界,失败时有什么现象,怎样最小复现,怎样写进团队流程。

text
concept -> boundary -> failure signal -> minimal proof -> project rule
1

工程切片 1:最小可复现样例 ​

**场景。**围绕 worker object 建一个很小的案例,不要一开始就放进完整业务系统。把问题压缩到一个可以提交给同事的目录,保留源码、构建命令、版本输出和预期现象。

**边界。**先判断这里讨论的是 worker object、queued connection 还是 取消 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> worker object -> queued connection -> 取消 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 2:接口边界 ​

**场景。**围绕 queued connection 建一个很小的案例,不要一开始就放进完整业务系统。写清调用方能依赖什么、不能依赖什么,并把隐式假设转换成命名函数、配置项或测试。

**边界。**先判断这里讨论的是 queued connection、取消 还是 关闭 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> queued connection -> 取消 -> 关闭 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 3:失败注入 ​

**场景。**围绕 取消 建一个很小的案例,不要一开始就放进完整业务系统。故意制造一个常见错误,让日志、断言或测试先失败,再用修复后的证据说明规则生效。

**边界。**先判断这里讨论的是 取消、关闭 还是 GUI 线程 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 取消 -> 关闭 -> GUI 线程 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 4:跨平台差异 ​

**场景。**围绕 关闭 建一个很小的案例,不要一开始就放进完整业务系统。至少比较 Windows、Linux 或不同编译器下的一个差异,记录差异属于标准、实现还是平台约定。

**边界。**先判断这里讨论的是 关闭、GUI 线程 还是 任务归属 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 关闭 -> GUI 线程 -> 任务归属 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 5:性能观察 ​

**场景。**围绕 GUI 线程 建一个很小的案例,不要一开始就放进完整业务系统。用小规模和压力规模各跑一次,区分算法成本、同步成本、I/O 成本和工具链配置成本。

**边界。**先判断这里讨论的是 GUI 线程、任务归属 还是 worker object 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> GUI 线程 -> 任务归属 -> worker object -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 6:生命周期 ​

**场景。**围绕 任务归属 建一个很小的案例,不要一开始就放进完整业务系统。标出资源创建、转移、共享、停止和销毁的顺序,特别关注错误返回和提前退出路径。

**边界。**先判断这里讨论的是 任务归属、worker object 还是 queued connection 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 任务归属 -> worker object -> queued connection -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 7:诊断证据 ​

**场景。**围绕 worker object 建一个很小的案例,不要一开始就放进完整业务系统。保留命令、日志、符号、栈、测试输出或截图,让结论可以被另一个环境重新验证。

**边界。**先判断这里讨论的是 worker object、queued connection 还是 取消 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> worker object -> queued connection -> 取消 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 8:维护策略 ​

**场景。**围绕 queued connection 建一个很小的案例,不要一开始就放进完整业务系统。把一次性经验沉淀到 README、CI、脚本、示例工程或检查表里,避免只存在个人记忆中。

**边界。**先判断这里讨论的是 queued connection、取消 还是 关闭 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> queued connection -> 取消 -> 关闭 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 9:版本演进 ​

**场景。**围绕 取消 建一个很小的案例,不要一开始就放进完整业务系统。为未来变更预留兼容策略:版本号、特性位、弃用窗口、迁移脚本和回滚入口。

**边界。**先判断这里讨论的是 取消、关闭 还是 GUI 线程 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 取消 -> 关闭 -> GUI 线程 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 10:最小消费者 ​

**场景。**围绕 关闭 建一个很小的案例,不要一开始就放进完整业务系统。从使用者角度写一个小程序或小工程,只依赖公开接口,验证安装包、头文件和运行时行为。

**边界。**先判断这里讨论的是 关闭、GUI 线程 还是 任务归属 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 关闭 -> GUI 线程 -> 任务归属 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 11:边界复盘 ​

**场景。**围绕 GUI 线程 建一个很小的案例,不要一开始就放进完整业务系统。用一张表说明问题发生在源码、编译、链接、加载、运行、关闭或发布中的哪一层。

**边界。**先判断这里讨论的是 GUI 线程、任务归属 还是 worker object 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> GUI 线程 -> 任务归属 -> worker object -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 12:反例训练 ​

**场景。**围绕 任务归属 建一个很小的案例,不要一开始就放进完整业务系统。写出一个看似能工作但不可维护的方案,再说明它在规模、团队协作或发布时会怎样失败。

**边界。**先判断这里讨论的是 任务归属、worker object 还是 queued connection 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 任务归属 -> worker object -> queued connection -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 13:最小可复现样例 ​

**场景。**围绕 worker object 建一个很小的案例,不要一开始就放进完整业务系统。把问题压缩到一个可以提交给同事的目录,保留源码、构建命令、版本输出和预期现象。

**边界。**先判断这里讨论的是 worker object、queued connection 还是 取消 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> worker object -> queued connection -> 取消 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 14:接口边界 ​

**场景。**围绕 queued connection 建一个很小的案例,不要一开始就放进完整业务系统。写清调用方能依赖什么、不能依赖什么,并把隐式假设转换成命名函数、配置项或测试。

**边界。**先判断这里讨论的是 queued connection、取消 还是 关闭 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> queued connection -> 取消 -> 关闭 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 15:失败注入 ​

**场景。**围绕 取消 建一个很小的案例,不要一开始就放进完整业务系统。故意制造一个常见错误,让日志、断言或测试先失败,再用修复后的证据说明规则生效。

**边界。**先判断这里讨论的是 取消、关闭 还是 GUI 线程 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 取消 -> 关闭 -> GUI 线程 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 16:跨平台差异 ​

**场景。**围绕 关闭 建一个很小的案例,不要一开始就放进完整业务系统。至少比较 Windows、Linux 或不同编译器下的一个差异,记录差异属于标准、实现还是平台约定。

**边界。**先判断这里讨论的是 关闭、GUI 线程 还是 任务归属 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 关闭 -> GUI 线程 -> 任务归属 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 17:性能观察 ​

**场景。**围绕 GUI 线程 建一个很小的案例,不要一开始就放进完整业务系统。用小规模和压力规模各跑一次,区分算法成本、同步成本、I/O 成本和工具链配置成本。

**边界。**先判断这里讨论的是 GUI 线程、任务归属 还是 worker object 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> GUI 线程 -> 任务归属 -> worker object -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 18:生命周期 ​

**场景。**围绕 任务归属 建一个很小的案例,不要一开始就放进完整业务系统。标出资源创建、转移、共享、停止和销毁的顺序,特别关注错误返回和提前退出路径。

**边界。**先判断这里讨论的是 任务归属、worker object 还是 queued connection 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 任务归属 -> worker object -> queued connection -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 19:诊断证据 ​

**场景。**围绕 worker object 建一个很小的案例,不要一开始就放进完整业务系统。保留命令、日志、符号、栈、测试输出或截图,让结论可以被另一个环境重新验证。

**边界。**先判断这里讨论的是 worker object、queued connection 还是 取消 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> worker object -> queued connection -> 取消 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 20:维护策略 ​

**场景。**围绕 queued connection 建一个很小的案例,不要一开始就放进完整业务系统。把一次性经验沉淀到 README、CI、脚本、示例工程或检查表里,避免只存在个人记忆中。

**边界。**先判断这里讨论的是 queued connection、取消 还是 关闭 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> queued connection -> 取消 -> 关闭 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 21:版本演进 ​

**场景。**围绕 取消 建一个很小的案例,不要一开始就放进完整业务系统。为未来变更预留兼容策略:版本号、特性位、弃用窗口、迁移脚本和回滚入口。

**边界。**先判断这里讨论的是 取消、关闭 还是 GUI 线程 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 取消 -> 关闭 -> GUI 线程 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 22:最小消费者 ​

**场景。**围绕 关闭 建一个很小的案例,不要一开始就放进完整业务系统。从使用者角度写一个小程序或小工程,只依赖公开接口,验证安装包、头文件和运行时行为。

**边界。**先判断这里讨论的是 关闭、GUI 线程 还是 任务归属 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 关闭 -> GUI 线程 -> 任务归属 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 23:边界复盘 ​

**场景。**围绕 GUI 线程 建一个很小的案例,不要一开始就放进完整业务系统。用一张表说明问题发生在源码、编译、链接、加载、运行、关闭或发布中的哪一层。

**边界。**先判断这里讨论的是 GUI 线程、任务归属 还是 worker object 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> GUI 线程 -> 任务归属 -> worker object -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 24:反例训练 ​

**场景。**围绕 任务归属 建一个很小的案例,不要一开始就放进完整业务系统。写出一个看似能工作但不可维护的方案,再说明它在规模、团队协作或发布时会怎样失败。

**边界。**先判断这里讨论的是 任务归属、worker object 还是 queued connection 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 任务归属 -> worker object -> queued connection -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

本篇收束 ​

掌握《Qt 线程、取消与关闭协议》的标志,不是记住所有名词,而是能把 worker object、queued connection、取消、关闭、GUI 线程、任务归属 放进一条可验证的工程链路。 先用最小样例建立判断,再用测试和脚本固定判断,最后把失败证据留给未来的自己和团队。

最后更新于:

Pager
下一篇← C++ 编程 / C++ Programming

持续记录,持续成长

Copyright © Tidenflow