DriveTask调度循环
概述 / Overview
`DriveTask` 是 Clumsy 内部把 `MovementDefinition` 的 迭代器变成 周期控制循环的工具。每次调用 `DriveTask.WaitDriveTask(IEnumerable<bool>)` : 1. 起一条 新线程(`ThreadPriority.AboveNormal`)。 2. 每隔 `Configuration.conf.DriveTaskInterval`(默认 20 ms)调一次 `enumerator.MoveNext()`。 3. 阻塞调用方直到迭代器结束(正常完成或异常)。
`DriveTask` is Clumsy's mechanism for turning a MovementDefinition's iterator into a timed control loop. Each `DriveTask.WaitDriveTask(IEnumerable<bool>)` call: 1. Spawns a new thread at `ThreadPriority.AboveNormal`. 2. Calls `enumerator.MoveNext()` every `Configuration.conf.DriveTaskInterval` (default 20 ms). 3. Blocks the caller until the iterator ends (success or exception).
实现:`D:\src\Clumsy\ClumsyCore\DriveTask.cs:34-205`。 Implementation: `D:\src\Clumsy\ClumsyCore\DriveTask.cs:34-205`.
标准用法 / Standard usage
DriveTask.WaitDriveTask(new SteeringLineFollowing
{
srcX = sx, srcY = sy,
dstX = dx, dstY = dy,
basespeed = 600
}.Follow());
// Returns when the Movement finishes (normal or thrown)
循环细节 / Loop details
WaitDriveTask(IEnumerable<bool>) 入口:
│
▼
new DriveTask:
enumerator = iterable.GetEnumerator();
Thread(prio=AboveNormal) starts:
│
▼ 循环 / loop:
if (stopToken) break;
bool keepGoing = enumerator.MoveNext();
if (!keepGoing) { stopped_reason = 0; break; }
Thread.Sleep(DriveTaskInterval); // 20 ms 默认
│
▼ 退出 / exit (any cause):
Monitor.PulseAll(wait)
│
▼
WaitDriveTask blocks on Monitor.Wait(wait)
└─→ 醒来后检查 exception,重新抛 / wakes, rethrows captured exception if any
`DoUntil` 并行检查 / Parallel termination check
`DoUntil(IEnumerable<bool> condition)` 起一条 并行任务,独立轮询 `condition`;若 `condition.MoveNext()` 返回 true,停止主 Movement 并设置 `stopped_reason = stop_condition_id`:
var driveTask = DriveTask.WaitDriveTaskBegin(new SteeringLineFollowing { ... }.Follow());
driveTask.DoUntil(new ObstacleCloseCheck { distThres = 200 }.Get());
driveTask.Wait(); // blocks; returns when either main Movement done OR DoUntil fires
`stopped_reason` 区分了正常完成 (0)、`DoUntil` 触发(其条件 id)、异常 (-1)。 `stopped_reason` differentiates normal (0), `DoUntil` triggered (the cond ID), and exception (-1).
与 SecurityCheck 的关系 / Relation to SecurityCheck
`PilotBase.SecurityCheck()`(`D:\src\Clumsy\ClumsyCore\Pilot\PilotDefinition.cs:64-75`)是 独立的看门狗,每个 Movement 都不感知它。SecurityCheck 监测 :
- 当前 DriveTask 是否在 `Configuration.conf.DriveTaskTimeout`(默认 300 ms)内有控制信号产生。
- 若没有 → 调 `DriveStop()` 强制车停 → 主 DriveTask 的 `stopToken = true` → 下一 tick 退出循环。
【发现】 SecurityCheck 不杀 Movement 的 enumerator;它只是让 DriveTask 在下一 tick 退出循环。所以 Movement 作者需自己确保 `Get()` 在迭代器内可被中断(不要 sleep 长时间)。
SecurityCheck doesn't kill the enumerator; it just lets the loop bail next tick. Movement authors must keep `Get()` interruptible (no long sleeps).
异常传播 / Exception propagation
- `MoveNext()` 抛异常 → 捕获到 `_exception` 字段 → `stopped_reason = -1` → 触发 `Monitor.PulseAll`。
- `WaitDriveTask` 调用方 醒来后重新抛该异常。
- 上游 `SimpleAgvInterface.Queue` 的 action 内异常 → 记到 `queueInfo[i].exception`,跳过本行剩余 action。
【注意】 不要把异常 吞在 Movement 内。让它传出,让上层决定恢复策略。
Don't swallow exceptions inside Movements; let them propagate so the upper layer decides.
Thread Priority 与延迟 / Thread priority & latency
- DriveTask 线程是 `AboveNormal`,比 LadderLogic 略高。
- tick 抖动主要来自 `Thread.Sleep` 精度(Windows ~1–15 ms 量级)。
- 高速 / 高精度场景(如 汽车面差检测)可能需要在 PLC 层做关键回路,DriveTask 仅做高层规划。
在 SimpleAgvInterface 中的使用 / Usage in SimpleAgvInterface
`SimpleAgvInterface.Queue` 的每个 action 内通常一次 `DriveTask.WaitDriveTask(...)`。详见 SimpleAgvInterface Queue机制。
Each action inside `SimpleAgvInterface.Queue` typically calls `DriveTask.WaitDriveTask` once.
配置 / Configuration
| 字段 / Field | 默认 / Default | 文件 / File |
|---|---|---|
| `DriveTaskInterval` | 20 ms | `D:\src\Clumsy\ClumsyCore\Configuration.cs` |
| `DriveTaskTimeout` | 300 ms | same |
| `basicSpeed` | 项目相关 | same |
从嵌入资源 `ClumsyCore.res.defaultconf.json` 加载,或用 `Configuration.FromFile(fn)` 覆盖。 Loaded from embedded `ClumsyCore.res.defaultconf.json` or overridden via `Configuration.FromFile`.
常见错误 / Common mistakes
- 忘了 `WaitDriveTask` 是阻塞的 → 在 UI 线程调会冻 UI。包到 `SimpleAgvInterface.Queue` 的 async action 里。
- Movement Get() 不 yield → 立即耗尽,DriveTask 立即返回,看起来"完成了 0 ms"。Always `yield return` at least once per control cycle.
- DriveStop 中无 vCmd = 0 → 即使 enumerator 停了,最后一帧的 vCmd 还在 DObject 里,车继续动。在 `DriveStop()` 里显式清零所有 IDriveWriter 状态。
- `WaitDriveTask` is blocking; calling it on the UI thread freezes UI. Wrap in `SimpleAgvInterface.Queue`'s async action.
- If `Get()` never yields, DriveTask exits immediately.
- `DriveStop()` must explicitly zero all IDriveWriter outputs; otherwise the last frame's `vCmd` stays in DObject.