Serial-Studio 深度解析:Spec 0034 声明式异步任务树 —— I/O 连接编排引擎的设计、缺陷审计与幸存实现
Serial-Studio 深度解析Spec 0034 声明式异步任务树 —— I/O 连接编排引擎的设计、缺陷审计与幸存实现【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-StudioSerial-Studio开源遥测仪表盘支持 UART、BLE、MQTT、Modbus、CAN Bus 等数据源在 2026 年 7 月曾对全量 I/O 驱动的连接生命周期做了一次系统性审计并据此写下 Spec 0034用声明式任务树Task Tree替代每个驱动各自手写的布尔状态机、阻塞等待与一次性信号 lambda。这篇文档完整记录了 18 个手工异步流程的缺陷盘点、五项缺陷归类、引擎的六种组合子语义、共享重试策略以及可验证的验收标准。值得注意的是该规范最终被标记为shelved搁置——连接流程层flow layer先落地后被整体移除但它的核心产物——任务树引擎Async::——至今存活在主数据路径之外服务于 MQTT 发布器重连与连接诊断探针。读完本文你将理解一个 Qt/C 遥测应用如何把连接-重试-握手-断线恢复从散落在各驱动的 1700 行顺序控制代码收敛为一个可单测、线程亲和、零锁的编排引擎。一、规范定位shelved 状态与当前存留物先厘清阅读前提避免把已删除的设计当成现状。spec.md 头部明确记载Shelved 2026-07-30 (commit 38c9ef66).The flow layer shipped and was then removed:IO::ConnectionFlowsand theHAL_Driverasync-open hooks are gone, driver opens are synchronous again, and drop recovery is per-driver. Only the engine survives —app/src/Async/(TaskTree, RetryPolicy, AsyncClock), used byMQTT::Publisherand the spec-0035 diagnostics probes.即2026-07-30 的版本中IO::ConnectionFlows流程组合层与HAL_Driver的异步打开钩子supportsAsyncOpen()/beginOpen()/abortOpen()被移除驱动打开回归同步断线恢复退回各驱动自行处理唯一幸存的是引擎本身TaskTree、RetryPolicy、AsyncClock它现在位于仓库的 core/Core/Async/ 目录规范写作时期路径为app/src/Async/仓库后续发生了目录重组被MQTT::Publisher与 spec 0035 的连接诊断探针使用。当前 I/O 契约以 doc/claude/architecture/io.md 为准。规范本身是四阶段文档流的第 1 阶段WHAT/WHY配套的 plan.md第 2 阶段 HOW与 tasks.md第 3 阶段任务拆解同目录存放三者共同构成了从问题审计到17 项可逐项验证的实现任务的完整闭环这也是本文的主要素材来源。二、问题起源18 个手工异步流程的盘点规范的立论基础不是一句代码很乱而是一次 2026-07-25 的实测审计。结论是应用里每一条连接生命周期——打开、重试、握手、拆除、以及断线后的重连——都是按驱动各自手写的混用布尔标志、一次性信号 lambda、阻塞等待和延迟invokeMethod续延不存在步骤、超时、重试、取消的共享概念。规范列出了 18 个不同的手工异步流程F1–F18及其当时的顺序控制机制#流程位置当时的机制超时重试取消F1带重试的 TCP 连接Network.cpp:238-256阻塞waitForConnected(600) 主线程QThread::msleep(300)5 次for循环600 ms/次5 次固定、300 ms 平间隔无——循环不可中断F2异步 DNS 解析Network.cpp:440-445,:512-524QHostInfo::lookupHost回调查找 id 被丢弃无无无——被取代的查找回调仍会踩写m_hostExistsF3Socket 错误拆除Network.cpp:529-549错误信号 →disconnectDevice 模态框m_connecting期间抑制n/an/a仅布尔抑制标志F4UDP 绑定 组播加入Network.cpp:182-230直线调用失败 →close()无无n/aF5主连接序列ConnectionManager.cpp:683-712,:860-865同步扇出DeviceManager::open丢弃驱动bool结果无无无——多源连接无法中途中止F6设备重建续延ConnectionManager.cpp:1274-1350三个手写invokeMethod(QueuedConnection)续延无无无——新重建不会使旧重建的排队重连失效F7关机/拆除ConnectionManager.cpp:882-907手工排空 map 到局部再销毁技巧抵御驱动销毁期间的重入configurationChangedn/an/a排序技巧不是守卫F8UI-驱动配置保存ConnectionManager.cpp:93-95,:1238单次QTimer750 ms 防抖n/an/a隐式F9BLE 发现→连接→服务发现→特征订阅BluetoothLE.cpp多段8 个 pending/probe 状态标志、跨服务探测重试循环、静态跨实例扇出列表驱动内全无超时仅探测循环close()disconnect(this)无 epoch 守卫errorOccurred从未被连接F10MQTT 源重连MQTT.cpp:1076-1108由 14 个 setter 调用布尔m_reconnectPending 堆上自释放 lambda无无意图布尔在 lambda 内复查F11MQTT 发布器重连Publisher.cpp:490-579worker 线程F10 的结构相同复刻 一个额外的排队finishPendingReconnect()跳板无无新读m_cfg.enabledF12MQTT 测试连接探针Publisher.cpp:688-724一次性客户端 内联 5000 msQTimerdone共享标志5000 ms内联字面量无done标志F13Modbus TCP 带重试连接Modbus.cpp:462-496阻塞嵌套QEventLoop::exec()msleep(300)5 次循环——F1 的近似拷贝800 ms/次5 次固定、300 ms 平间隔无F14Modbus 轮询周期Modbus.cpp:1170-1340手写顺序迭代器QModbusReply*指针同一性做在途守卫Qt Modbus 设备超时委托给 Qt Modbus停定时器 deleteLaterF15UART 资源丢失自动重连UART.cpp:781-809全应用唯一的断线恢复m_pendingReconnect从共享 1 HzTimerEvents滴答轮询无电平触发、无上限、无退避close()清标志F16进程启动/崩溃响应Process.cpp:175-531阻塞waitForStarted(3000)崩溃仅报告并断开从不重启3000/2000 ms 阻塞等待无m_pipeRunning原子量F17USB 传输池 排空USB.cpp多段自重提交 iso 回调拆除时msleep(5)忙轮询到 2000 ms 截止每传输 libusb 超时无失败硬停原子量 有界排空F18X/Y/ZMODEM 传输状态机XMODEM.h:66-74等三个手写枚举状态机7/10/87 相位各自超时定时器与重试计数ZMODEM 另有singleShot(0)自让出分块循环10–15 s 单次10 次、无退避cancelTransfer()ZMODEM 重查是全栈唯一显式陈旧续延守卫从这张表直接归纳出五类缺陷每一类都有具体证据坏地址冻结 UI。F1 在主线程阻塞最长约 4.5 s5 × 600 ms 等待 4 × 300 ms 睡眠且显示等待光标F13 用嵌套事件循环阻塞约 6 s。取消不可能——因为处理取消的代码本身就在被阻塞的代码里。断线几乎不恢复。只有 UARTF15在非用户请求的断线后重试且是无上限、无退避的 1 Hz 电平触发。F10/F11 只在配置被编辑后才重新打开F16 报告进程崩溃但从不重启。陈旧异步回调靠临时手段或完全不设防。F2、F6、F9、F16 没有 generation/epoch 令牌现存防护彼此互不相同一个QPointer自动置空、一个回复指针同一性检查、两个布尔抑制标志、BLE 跨实例转发里的两个静态重入布尔、以及 ZMODEM 里唯一一次显式续延重查。错误被丢在地上。F5 丢弃驱动的打开结果F9 从未连接 BLE 控制器的错误信号两个 BLE 错误处理器都有静默的default:分支。连接失败最终只表现为未连接没有原因。同一策略写了五遍、五组常量。F1 用 5 次/600 ms/300 msF13 用 5 次/800 ms/300 msF18 三处独立实现 10 次 10–15 s 超时且无退避。没有一处做几何退避ZMODEM 的重试计数器只在传输开始时清零、成功数据块后不清零——重试次数在整个会话内累积这是一个真实缺陷共享且被测过的策略本可避免它。成本也是结构性的在最典型的驱动BLE里约 1140 行非表格代码中约 550 行是顺序控制、标志腾挪与跨实例状态传播而真正调用 Qt Bluetooth 的只有约 130 行——编排与工作比 4:1。整个 I/O 栈顺序控制代码约 1700 行无一共享每个新驱动都要重新推导这套脚手架每个重连 bug 都只在一个驱动里修复。三、目标与非目标目标Goals连接生命周期表达为可组合任务树——带内建超时、取消与错误传播的顺序/并行分组——而非按驱动的布尔状态机重试与退避策略只写一遍、共享重连修复对所有订阅流程生效连接尝试永不阻塞 UI取消/断开在任何时点立即生效流中断线自动恢复且反复恢复不累积定时器、信号连接、socket 或设备对象失败步骤报告哪个步骤失败及原因形态可被诊断工作spec 0035与问题中心spec 0033消费编排引擎可无硬件测试用假步骤在 C 单测层断言顺序、超时、重试、取消语义。非目标Non-Goals同样写得很具体值得逐条读不做用户可见的 UI 重设计不重写驱动——驱动保留传输代码只有其外围顺序控制迁移未迁移的驱动原样工作按流程选择性迁移v1 只迁移 18 个流程中缺陷记录最重的两个R7不引入任何新线程、线程池或互斥量——它是纯事件循环构造物帧热路径零改动——任务树只在连接生命周期边界运行无逐帧工作、无新热路径信号跳板、无缓存标志新输入不是通用作业系统导出 worker、MQTT 发布 worker、播放器 worker、FrameConsumer保持现有线程模型不持久化在途流程不做诊断 UI产生结构化步骤失败在范围内展示归 spec 0033/0035。四、需求R1–R11规范定义了 11 条可验证需求R1 可组合流程连接生命周期声明为步骤树顺序/并行分组声明读起来就是序列本身而非一组靠标志推断位置的回调。R2 每步都有截止期任何步骤可声明超时到期对外围分组而言与步骤失败不可区分且步骤自身资源被释放。流程因此永不永久挂起——这正是 BLEF9 无任何超时的当前状态。R3 取消即时且彻底取消运行中的流程停止当前步骤、释放其持有物、不再运行后续步骤取消在序列的每个点可用包括退避等待期间。没有步骤阻塞所在线程UI 发出的取消总会被处理。R4 错误带来源传播失败步骤报告机器可读的步骤标识 人类可读原因外层分组传播第一个失败流程向属主报告。无失败被静默丢弃无错误路径终止于未处理的default:。R5 唯一重试策略带退避的重试只存在于一个位置——一个命名策略对象携带退避表、尝试上限与重置规则。每个重试流程都用它任何流程不得自带间隔或尝试循环。R6 断线自动恢复成功打开后非用户请求地掉线属主流程在重试策略下重跑打开序列直到成功、达到上限或用户取消。恢复发出与手动重连相同的连接状态信号——不多于、且顺序相同。R7 v1 迁移集合TCP 连接/重试/重连路径F1及 F2/F3 作为支撑步骤与 MQTT 重连路径F10 F11 收敛到一个共享策略。BLEF9归 v2其余流程 v1 不动。R8 迁移流程行为对等所有当前用户可观察结果保持相同成败终态、信号、错误文案、持久化设置。两个刻意差异即变更本身连接不再阻塞界面R3、断线会恢复R6。R9 跨周期无泄漏流程运行到完成/失败/取消释放其创建的全部定时器、信号连接、socket 与驱动对象重复 连接/断开/恢复 周期回到稳态。R10 无硬件可测引擎的顺序、超时、重试、取消、错误传播语义可用假步骤与可控时钟断言进程内、无设备/socket/broker。R11 渐进采用未迁移驱动继续走现有同步路径无需编辑加入编排层按驱动选择性启用。五、引擎设计六种组合子 一个运行器plan.md 给出第 2 阶段设计在app/src/Async/下新增一个小型、仓内、仅事件循环的任务树引擎——六个组合子顺序分组、并行分组、超时、重试、等待信号、同步调用加一个拥有根节点并在析构时取消的运行器——外加一个共享Async::RetryPolicy承载退避表与尝试上限。引擎语义被逐条写明供代码存在之前 review 否决TaskQObject有start()/cancel()槽与一个finished(Outcome, StepError)信号。Outcome取Success、Failure、Cancelled、TimedOut。任务恰好发出一次finished引擎对此断言。SequentialGroup子节点i1仅在子节点i报告Success后启动任何其他结果即以该结果与该子节点的StepError结束分组后续子节点不再启动。ParallelGroup一次启动所有子节点第一个非Success取消其余兄弟并以该结果结束。为 v2 的 BLE 预留v1 流程全部是顺序的。TimeoutTask包裹一个子节点到期取消子节点并以TimedOut结束——对父级与失败不可区分R2。RetryTask包裹一个子节点与一个RetryPolicy子节点失败后等待策略的当前延迟再重启取消发生在等待期间则停止等待、不再做任何事R3成功重置尝试计数。SignalTask在一个QPointer守卫的发送者上连接一个成功信号与任意多个失败信号第一个触发者结束步骤可携带可选 abort 回调这正是cancel()到达QHostInfo::abortHostLookup与QAbstractSocket::abort的途径。InvokeTask同步运行std::functionbool(QString)并映射结果。未迁移驱动经由它进入树中保持今日语义R11。TaskRunner以std::unique_ptrTask持有根暴露run()/cancel()/isRunning()析构时取消。它是调用方持有的唯一对象引擎之外没有任何代码触碰裸Task指针。线程模型是线程亲和而非线程安全树中每个Task生活在创建其TaskRunner的线程上内部连接在该线程内解析为直接调用定时器即该线程的定时器两个线程上的两个运行器互不共享——RetryPolicy是值类型拷贝进各树。Network 流程在主线程运行发布器流程运行在已存在的 worker 线程上这恰好证明引擎不是仅主线程。构造函数断言线程亲和错误线程构造在调试期响亮失败而非发布期竞态。驱动迁移钩子的默认值被刻意选成不可能改变未迁移驱动行为supportsAsyncOpen()基类返回falsebeginOpen(mode)默认调用open(mode)并同步发射openFinished(result, reason)——对每个未重写者逐字节等同今日abortOpen()默认close()linkDropped()仅当成功打开过的链路在无关闭请求下掉线时发射。五.1 幸存实现core/Core/Async 的三个文件规范写作时引擎在app/src/Async/当前仓库中它位于 core/Core/Async/与 plan 描述高度一致且已进入构建系统core/Core/CMakeLists.txt 注册了Async/AsyncClock.h、Async/RetryPolicy.h/.cpp、Async/TaskTree.h/.cpp。逐文件看TaskTree.h完整保留了 plan 的全部词汇并补充了组合器自由函数使流程声明读起来就是序列本身[[nodiscard]] ParallelGroup* parallel(QString name); [[nodiscard]] SignalTask* awaitSignal(QString name); [[nodiscard]] SequentialGroup* sequential(QString name); [[nodiscard]] InvokeTask* invoke(QString name, InvokeTask::Callable callable); [[nodiscard]] TimeoutTask* timeout(Task* child, int msec, AsyncClock clock); [[nodiscard]] RetryTask* retry(Task* child, const RetryPolicy policy, AsyncClock clock); Task* onDone(Task* task, std::functionvoid(Outcome, const StepError) handler);头文件注释直接呼应规范条款StepError的stepreason字段以诊断与问题中心规范可消费、无需解析散文的形态携带失败来源R4RetryTask注释明确尝试是内部的包装器在子节点最终成功、达到上限或流程被取消之前不发任何信号因此恢复中的链路不会放大连接状态抖动——这正是 plan 风险一节指出的最可能的静默破坏向量每次连接/断开都会 bump 帧池代际并驱动 Dashboard 流可用性100 次断线绝不能变成数百次池失效。SignalTask把连接推迟到doStart()步骤到达前触发的信号永远无法完成它onSuccess/onFailure模板用QPointer捕获发送者工厂闭包在发送者已死时返回无效连接——使被取代的续延不可能而不只是不太可能。RetryPolicy.cpp是 R5策略只写一遍的落点文件头注释直书// Constants: the whole retry schedule of the application lives here and nowhere else两组命名策略的常量策略尝试上限初始延迟延迟上限增长因子语义initialConnect()5300 ms300 ms1.0平间隔用户显式请求的连接背后是等待光标总预算必须匹配迁移前阻塞等待而非超过它autoReconnect()60500 ms5000 ms2.0几何打开后自行掉线的链路约 4.5 分钟预算实现细节值得注意delayForAttempt()的增长循环被kBackoffStepCap 24封顶防止大上限策略的延迟计算失控shouldRetry()要求completed_attempts 1 maxAttempts即首失败必重试空间在计数语义上是明确的。initialConnect()从400 ms 几何爬到 3000 ms 上限改为300 ms 平间隔 × 5的修订记录在 tasks.md 的 T5 后记中理由与上表一致。AsyncClock.h是 R10无硬件可测的接缝抽象AsyncClockschedule(int msec, Callback)/cancel(TimerId)SystemClock基于QObject::startTimer的事件循环定时器。SystemClock的每个方法都带SS_ASSERT(QThread::currentThread() thread(), ...)——定时器永远属于驱动该树的线程线程亲和不是约定而是断言。六、引擎的现存消费者MQTT 发布器重连与诊断探针规范称只有引擎幸存仓库中确实可以找到它的三处真实用法。MQTT 发布器 worker 重连core/Storage/MQTT/PublisherWorker.cpp是 R6 语义的现役实现。buildReconnectFlow()组合出一棵与 plan 图同构的树auto* group Async::sequential(QStringLiteral(mqtt-publisher-reconnect)); auto* wait Async::awaitSignal(QStringLiteral(broker-disconnect)); group-addChild(Async::timeout(wait, kBrokerDisconnectTimeoutMs, m_runner-clock())); ... auto* attempt Async::sequential(QStringLiteral(broker-open)); auto* connected Async::awaitSignal(QStringLiteral(broker-connect)); attempt-addChild(Async::timeout(connected, kBrokerConnectTimeoutMs, m_runner-clock())); group-addChild(Async::retry(attempt, Async::RetryPolicy::autoReconnect(), m_runner-clock()));即sequential[ broker-disconnect(带超时), retry(broker-open: 拨号 broker-connect(带 15 s 超时), autoReconnect 策略) ]由bootstrap()在worker 线程上创建的TaskRunner驱动m_runner std::make_uniqueAsync::TaskRunner(this);。这正是 tasks.md T14 记载的迁移结果原先 worker 侧手写的m_reconnectPending布尔 堆上自释放QMetaObject::Connection*lambda 被一棵树取代~PublisherWorker只需丢弃 runner——运行器析构即取消替换了手工断开。连接诊断探针spec 0035 侧也跑在同一引擎上ConnectionDiagnostics.cpp 持有Async::TaskRunner m_runner把诊断组包成sequential(connection-diagnostics)并整体套一层Async::timeout(group, kRunTimeoutMs, ...)——R2整个流程永不挂起在探针上的直接体现NetworkChecks.cpp 的可达性检查是sequential[ timeout(HostLookupTask, kLookupTimeoutMs), timeout(TcpProbeTask, kConnectTimeoutMs) ]两个自定义探针任务复用引擎的超时与顺序语义。七、无硬件测试tst_async_engine 的 30 个用例R10 的落地是 app/tests/tst_async_engine.cpp869 行pytest/ctest之外的 C 单测层由 app/tests/CMakeLists.txt 注册为tst_async_engine仅依赖Qt6::Core。测试双精度设计本身值得借鉴VirtualClockAsyncClock的测试替身schedule()只把(due, callback)存入哈希表并记录延迟序列advance(qint64 msec)推进虚拟时间并依次触发到期回调。多轮退避调度的断言因此发生在微秒级墙钟内而不是真实秒数——tasks.md 称之为AC2/AC3 必须是多秒测试而 R3 测试层的存在就是为了避免这种慢测试。FakeStep手控完成的任务记录starts()/ 取消次数与日志使兄弟取消恰好一次后续步骤不再启动等声明可被断言。覆盖点即 spec 的 AC1–AC3顺序保序、首个失败传播带填充的StepError、并行完成与兄弟取消、超时到期以TimedOut结束、精确退避序列与上限对虚拟时钟、成功重置尝试计数、中途步骤取消与中途退避取消均不再做任何事、以及所有任务形态的finished恰好一次纪律。八、验收标准与集成测试spec 的 10 条验收标准AC1–AC10把引擎语义与系统行为分层验证AC1–AC3C 单测层假步骤套件断言顺序/并行/首败传播永不完成的步骤被超时判负步骤中取消不运行后续步骤、退避中取消不再重试重试策略产生指定退避序列、到上限停止、成功重置计数。全部对可控时钟、以毫秒计墙钟。AC4维护者观察TCP 源指向不可达地址按连接界面全程保持响应尝试中按断开立即回到空闲。现状是窗口冻结约 4.5 s。AC5集成测试roadmap R10 的正式验收标准API server 开在 7777 端口的运行中应用测试在流中断开一条活动 TCP 链路100 次每次断开后源自行重连、帧恢复第 100 轮后进程处于稳态——打开设备数、活动流程数、连接数零增长。AC6同样的断开循环对 MQTT 源与发布器都恢复且共用一个策略。AC7既有驱动/API 连接测试原样通过。AC8失败连接报告哪个步骤失败地址解析、socket 连接、订阅而非裸超时。AC9v1 diff 未编辑任何未迁移驱动除共享基类钩子。AC10--benchmark-hotpath门限不变——负检查因为根本不该有热路径编辑。tasks.md 记载了集成测试tests/integration/test_link_recovery.py的落地细节14 个用例测试自己充当对端SeverablePeer一个可切断已接受 socket 的 loopback 监听器100 次断开以 10 组 × 10 的parametrize分块共享一条模块级链路——因为 100 轮各带 API 往返无法可靠塞进 30 s 单测上限test_steady_state_after_severance_loop对比基线activeFlows并断言对端恰好持有 1 个 socket、恰好接受1 severances个连接作为设备/连接数不增长的进程侧替代断言MQTT 侧用 broker 代理实现可切断的会话非商业构建跳过。九、约束与关键权衡为什么不直接链接 Qt TaskTreeplan.md 的决策表D1–D9是全文最硬核的部分其中 D1引擎来源的论证值得完整转述因为它同时回答了为什么不引入成熟库决策备选项选择与理由D1 引擎来源(A) 链接 Qt 6.11 的Qt6::TaskTree(B) vendor Qt Creator 树内solutions/tasking(C) 仓内最小引擎(C)六个理由D2 谁持有运行中流程全局 runner / 每驱动自持 / DeviceManager 每设备一个DeviceManager它已是每设备生命周期属主持有FrameReader并做拆除每设备取消与销毁免费D3 策略归属每驱动常量 / 一个共享策略对象一个共享RetryPolicy按用途命名initialConnect/autoReconnect构造上消灭第五类缺陷D4 线程模型线程安全锁/ 线程亲和线程亲和仓库 I/O 代码原则上禁止互斥量且各驱动对象本就有固定线程归属D5 迁移机制一次性全迁 / 带保行为默认值虚钩子虚钩子 默认值v1 diff 不碰 UART、BLE、Modbus、CAN、Audio、USB、HID、ProcessD6 v1 迁移集合先 BLE行数收益最大/ 先 Network MQTTNetwork MQTTBLE 编排面最大但测试覆盖最少、设备行为最多是最差的首验对象D7 断线检测定时器轮询 / 驱动发射linkDropped()驱动发射轮询重新引入每驱动定时器与固定时延下限D8 单测接缝真实QTimer/ 可注入时钟AsyncClock一处函数间接D9 引擎位置Misc// 新建Async/新建目录有自己的单测套件与文档契约的子模块配得上目录D1 选 (C) 的六条理由许可。商业构建分发专有二进制。Qt 官方 TaskTree 是 Technology Preview仅以 Qt 商业许可/LGPLv3/GPLv2 提供Qt Creator 树内 tasking 的 SPDX 头是 Qt Creator 自己的若为通常的 GPL-with-exception 表述则直接出局。仓内引擎携带仓库自己的GPL-3.0-only OR LicenseRef-SerialStudio-Commercial头零疑问。稳定性。Technology Preview 意味着 6.12 可以改 API把遥测应用的连接层建在上面是用有界维护成本换无界成本。构建与部署面。新增 Qt 组件须出现在三平台全部 CI 的 Qt 安装中并穿过三条部署路径Preview 模块在 Qt 安装器中未必默认选中。匹配度。需要的子集是六个组合子Tasking 附带任务适配器、存储、条件、迭代器、屏障与网络/进程包装——为一个顺序 超时 重试背上一个通用作业系统。质量门。lib/下 vendor 代码以-w编译且在code-verify.py范围之外拥有连接生命周期的代码应在仓库 linter、Power-of-Ten 规则与单测层之内。先例。仓库自建有图标注册表、命令注册表、SIMD 层与撤销历史而非为此引入框架。规范还记录了一条可机械执行的退路若仓内引擎超过约 1000 行、或嵌套取消/步骤存储语义比六个组合子暗示的更微妙则切换到 (A)为此仓内词汇刻意镜像 Tasking 的命名Group、Sequential、Parallel、Timeout、onDone且所有驱动侧调用都经过组合层切换时只有引擎文件变化。约束清单Constraints Invariants另有数条硬边界热路径零接触Driver → FrameReader → FrameBuilder → Dashboard路径、其三处Qt::DirectConnection跳板、缓存热路径标志输入全部不变无新线程无互斥量FrameReader/CircularBuffer保持主线程 SPSC重连不得放大连接状态抖动每次真实状态变化恰好一次转换时间戳归属不变源仍在驱动边界打戳重连不重戳、不回补无重组件spec 0030 约束不超过单个头文件友好库双许可模型是任何依赖的硬过滤spec 0001 的组合根构造顺序不动commercial/GPL 驱动分裂维持原状MQTT 是 commercial 构建源与其共享的编排代码必须在 GPL 构建下无它也能编译既有驱动契约成立configurationOk()仍查 UI 驱动、活动驱动允许空设备列表、每个驱动仍从构造函数发射configurationChanged()。十、未决问题与后续走向规范诚实地保留了六条未决问题库还是仓内调研发现 roadmap 假设的BSD 许可候选名实混淆见 D1需在许可核对后定夺——最终以 (C) 落地。退避表是什么建议短首延瞬时抖动无感恢复、几何增长至数秒上限、用户发起的连接重置尝试上限。精确数字与用户明确要求保持连接的源上限是否有限是维护者决策——当前实现给出了答案autoReconnect为 60 次、500 ms → 5000 ms 几何约 4.5 分钟预算且用户发起连接为 5 次平间隔 300 ms。自动恢复默认开吗R6 相对 R8 对等规则是真正的行为新增。建议对用户显式连接的源默认开启因为静默死亡正是 roadmap 点名的缺陷。用户是否看到恢复发生建议 v1 仅通过既有连接状态暴露 retrying展示归 spec 0035。API 是否暴露流程状态AC5 的泄漏断言需要可经 API 观测的活动流程计数——以io.getStatus新增linkState/reconnectAttempt/activeFlows三字段落地tasks.md T15复用现有状态字段优先于新增。v2 走多远F9BLE按行数是最大收益但测试覆盖最少作为单次迁移还是拆分发现、然后服务/特征建立是 plan 阶段决策——T18 最终以sequential[ble-discovery, ble-dial, ble-connect, parallel[ble-services, ble-subscribe]]的形态完成GATT 阶段刻意用ParallelGroup因为驱动从onServiceDiscoveryFinished()内部宣告gattReady()任何晚于该处理器返回的订阅步骤都会等待一个已过的边沿。十一、shelved 的教训引擎为什么留下了流程层为什么被删了这份 spec 最有价值的部分不是最终代码而是它完整记录了一次先落地、后回撤的工程决策轨迹T12 的 F3 跟进揭示了自动恢复已接线但无效力的隐蔽形态peer 关闭会先触发errorOccurred(RemoteHostClosedError)旧错误处理器在linkDropped()到达监督者之前就把流程拆掉了AC5 永远无法通过。修复是把受监督流程持有的 TCP 链路错误路由进linkDropped()以socketType() TcpSocket m_userWantsOpen isSignalConnected(linkDropped)三重守卫拆除与模态只在恢复放弃或用户断开时发生。**T19UART 自动重连**暴露了策略错配DeviceManager曾把每个受监督流程包在initialConnect()5 次、平 300 ms里——对用户等待其后的连接是对的对断线是错的30 秒后才重新出现的串口永远追不上。修复是RetryTask::setPolicy() 监督者切换到autoReconnect()60 次、500 ms → 5 s 几何并顺带修正了 Network 与 MQTT 监督同样的预算过短问题。connectedChanged链条的调查tasks.md Deferred 一节记录了一个不能一行修复的信号契约问题它是isConnected、readOnly、readWrite、connectedDeviceCount的 NOTIFY也是每设备连接状态的事实通知器四个消费者导出、文件传输、终端、运行时对话框无状态复查地直接行动——真修复是逐消费者守卫需要自己的 spec。这些记录合起来给出了引擎留下、流程层移除的合理解读Async::引擎本身小而正交无单例、无 GUI 依赖、无 I/O 知识RetryPolicy是纯值类型被 MQTT 发布器 worker 与诊断探针稳定消费且其单测套件自足而把每个驱动的打开路径重构为受监督流程所引入的耦合错误信号路由、模态对话框时序、connectedChanged拓扑契约超出了可维护边界2026-07-30 的回撤让驱动打开回归同步、断线恢复退回按驱动处理当前契约沉淀在 doc/claude/architecture/io.md。十二、延伸阅读规范三件套spec.md、plan.md、tasks.md含 T1–T21 逐项完成记录与验收状态当前 I/O 架构契约doc/claude/architecture/io.md引擎源码core/Core/Async/TaskTree.h、core/Core/Async/RetryPolicy.h、core/Core/Async/RetryPolicy.cpp、core/Core/Async/AsyncClock.h现役消费者core/Storage/MQTT/PublisherWorker.cppbuildReconnectFlow()、core/Ui/Misc/ConnectionDiagnostics.cpp、core/Ui/Misc/Diagnostics/NetworkChecks.cpp引擎单测app/tests/tst_async_engine.cppVirtualClockFakeStep覆盖 AC1–AC3集成测试维护者运行API server 7777tests/integration/ 中的test_link_recovery.py100 次断线循环 稳态断言对 Qt/C 应用开发者而言这份规范的可迁移经验有三其一先把全部手工异步流程做成一张带机制/超时/重试/取消四列的盘点表缺陷归类会自己浮出来其二重试策略做成值类型常量集中文件the whole retry schedule of the application lives here and nowhere else比任何状态机重构都更先产生价值其三为可无硬件测试预留一个时钟接缝——一两个虚函数换来整棵异步树在毫秒级墙钟内的完整语义断言。【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考