diff --git a/StandardScene.Magnetic/Scheduler/EventCarMission.cs b/StandardScene.Magnetic/Scheduler/EventCarMission.cs index 451f462..2d92eaf 100644 --- a/StandardScene.Magnetic/Scheduler/EventCarMission.cs +++ b/StandardScene.Magnetic/Scheduler/EventCarMission.cs @@ -8,6 +8,7 @@ using SimpleCore.PropType; using StandardScene; using StandardScene.Scheduler.FassEvent; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -68,6 +69,9 @@ namespace StandardScene.Scheduler /// 记录每辆车上一周期的 CurrentNode,用于 PrevNode 触发与地标变化时清除任务防重复标记。 [JsonIgnore] private readonly Dictionary _prevNodeByCarId = new Dictionary(); + /// 每车最多一个在飞 Tick,避免断网重试把 Task.Run 堆爆、同一车并发下发。 + [JsonIgnore] private readonly ConcurrentDictionary _tickInFlight = new ConcurrentDictionary(); + /// 进程运行状态扩展字段。 public class EventCarMissionStatus : MissionStatus { @@ -140,9 +144,9 @@ namespace StandardScene.Scheduler Diagnosis.Post($"FASS EventCar 配置已加载:{path},事件数={_eventCars.Count}", "FassEventCar", true); } - /// 停止轮询并等待工作线程结束(最多 2 秒)。 + /// 停止轮询并等待工作线程结束(最多 2 秒)。覆盖基类 Stop,避免全量重载后线程仍在对全场车下发。 [MethodMember(Name = "停止进程", Description = "停止 FASS 车辆事件轮询")] - public void Stop() + public override void Stop() { _started = false; try @@ -153,8 +157,10 @@ namespace StandardScene.Scheduler { } - status.status = "已停止"; + _workerThread = null; Diagnosis.Post("FASS车辆事件进程已停止", "FassEventCar", true); + base.Stop(); + status.status = "已停止"; } /// @@ -182,14 +188,18 @@ namespace StandardScene.Scheduler } /// - /// 遍历所有 Car,构建快照并异步调用 Handler(与 FASS Task.Run 行为一致)。 - /// - /// 跳过 status 为空或 usage.refreshing=false 的车辆(未参与调度刷新的车不处理)。 - /// 当地标变化时清除 ,允许在新地标重新触发事件。 - /// + /// 遍历所有 Car,构建快照后 Task.Run 处理:一车断网不堵其它车。 + /// 同一车上一拍未结束则跳过本拍,禁止无界堆积。 /// private void ProcessAllCars() { + var handler = _handler; + var eventCars = _eventCars; + if (handler == null || eventCars == null) + { + return; + } + foreach (var car in SimpleLib.GetAllCars().OfType()) { if (car.status == null) @@ -207,13 +217,37 @@ namespace StandardScene.Scheduler _prevNodeByCarId.TryGetValue(car.id, out var prevNode); var snapshot = FassEventCarSnapshotFactory.Create(car, prevNode, FassEventCarSnapshotFactory.GetNextNodeCode(car)); - Task.Run(() => _handler.HandleCarTimerTick(car, snapshot, _eventCars)); - if (!string.Equals(prevNode, currentNode, StringComparison.OrdinalIgnoreCase)) { _prevNodeByCarId[car.id] = currentNode; ClearTaskTags(car); } + + if (!_tickInFlight.TryAdd(car.id, 0)) + { + continue; + } + + var carId = car.id; + Task.Run(() => + { + try + { + if (_started) + handler.HandleCarTimerTick(car, snapshot, eventCars); + } + catch (Exception ex) + { + Diagnosis.Post( + $"FASS车辆事件处理异常 car={carId}:{ExceptionFormatter.FormatEx(ex)}", + "FassEventCar", + true); + } + finally + { + _tickInFlight.TryRemove(carId, out _); + } + }); } } diff --git a/StandardScene.Magnetic/Scheduler/FassEvent/FassEventCarHandler.cs b/StandardScene.Magnetic/Scheduler/FassEvent/FassEventCarHandler.cs index 4786507..fce9010 100644 --- a/StandardScene.Magnetic/Scheduler/FassEvent/FassEventCarHandler.cs +++ b/StandardScene.Magnetic/Scheduler/FassEvent/FassEventCarHandler.cs @@ -43,7 +43,7 @@ namespace StandardScene.Scheduler.FassEvent } /// - /// 单次 TimerTick 对一辆车的处理入口(通常在 Task.Run 中调用)。 + /// 单次 TimerTick 对一辆车的处理入口(由 EventCarMission 按车 Task.Run,同车同时只跑一拍)。 /// 仅处理 TriggerEvent=TimerTick 的配置项。 /// public void HandleCarTimerTick(Car car, FassEventCarSnapshot snapshot, IReadOnlyList eventCars)