DriveTask调度循环

来自MDCS wiki2
Artheru讨论 | 贡献2026年5月16日 (六) 22:00的版本 (Initial bilingual draft (auto-published))
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索


概述 / 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.

相关页面 / See also