C#模拟电梯控制系统:状态机与LOOK调度算法实战解析 简介这套C#模拟电梯控制源码面向具备一定C#基础、想深入理解面向对象与事件驱动开发的学习者。项目基于Windows平台从零演示电梯调度模拟系统的完整代码结构涵盖楼层与乘客对象建模、按钮事件与委托订阅、多线程同步、状态机管理开关门和升降以及最短路径调度等算法实现。例如电梯类设有当前楼层、目标楼层、载客量等属性并提供上升、下降、开门、关门方法楼层类管理上下行按钮状态。压缩包共27个文件以6个C#源文件为骨架辅以可执行程序、窗体资源、调试符号及项目配置包体仅246KB可直接在Visual Studio中加载运行。目前已有282人学习下载。通过阅读源码可以看清楼层请求如何被事件触发、电梯如何按优先级队列确定停靠顺序、界面状态如何刷新以及异常处理和单元测试如何融入工程配合设计文件和图标资源能快速梳理一个桌面应用的模块划分适合用作课程设计或入门练手。1. C# 模拟电梯控制源码为什么这个老题目至今仍是锻炼调度与状态机设计的最佳练手项目如果你搜索“C# 模拟电梯控制源码”大概率会看到一堆课程设计级别的代码一个窗体、几行 if else、电梯上下乱跑。但真正的电梯控制系统是一个典型的实时调度系统它要同时处理轿厢指令、楼层外呼、门机时序、超载与故障还要在所有请求之间做出让平均等待时间最短的决策。用 C# 写一遍这个模拟本质是在用最少的硬件成本去复刻一个多线程状态机 调度算法的完整闭环。它适合正在学 C# 面向对象设计的开发者适合准备面试中调度算法题的求职者也适合需要在工控或物联网项目里快速验证调度逻辑的嵌入式工程师。这篇文不打算给你一个可以直接交作业的完整源码包而是把控制系统的分层拆开给你一套能自己写、能调参数、能验证对错的最小实现框架。你会看到状态机怎么建模、调度器管什么、参数怎么设以及那些翻车率极高的边界坑。2. 电梯控制系统的核心状态机与调度器的职责边界2.1 电梯到底有哪几种状态状态图为什么要先画再写代码很多模拟源码一上来就写if (currentFloor targetFloor) MoveUp()这种代码跑通三五层楼没问题但一加入门机时序和顺向截停就会乱成一团。原因在于它把“状态”散落在 if else 里而不是收敛成一个可枚举的状态集合。我一般会先定义电梯的六个核心状态Idle无指令轿厢停在当前楼层门处于关闭状态Moving轿厢朝目标楼层运行细分上行或下行Decelerating到达目标楼层前的减速阶段此时不再响应新的顺向截停DoorOpening开门过程耗时几百毫秒DoorOpenWaiting开门等待等待乘客进出时间可配置DoorClosing关门过程需检测门区障碍故障状态我放在另一个枚举里单独处理避免和正常运行的状态搅在一起。为什么要把 DoorOpening 和 DoorOpenWaiting 拆成两个状态因为门机是有动作时间的拆开后你可以精确模拟“开门瞬间外部又按了同方向请求”这种情况——门还没开完要不要重新计时真实电梯里门开到位后等待时间重新计算所以这两个状态必须分开。状态图的价值在于把所有合法转移画出来写代码时只需要对着图补全 switch 或状态模式的分支。常见做法是先画一个状态转移表列出“当前状态 事件 → 下一状态 动作”。比如 Moving 到达目标楼层 → Decelerating 触发减速Decelerating 速度减到零 → DoorOpening 发开门指令。先把这张表写出来再去写类实现时基本不会漏分支。public enum ElevatorState { Idle, // 空闲 Moving, // 运行中 Decelerating, // 减速中 DoorOpening, // 开门过程 DoorOpenWaiting, // 开门等待 DoorClosing // 关门过程 }这段枚举定义了状态机的全部合法状态。注意我没有把 Up 和 Down 放进状态里而是用单独的Direction属性表示因为方向是属性状态是行为把两者混在一起会导致状态数量翻倍、转移条件混乱。2.2 调度器管方向不管理楼层指令与请求分层模拟电梯最常见的架构错误是让电梯类自己决定“我现在要去几楼”。正确做法是调度器只决定方向和下一个目标楼层电梯类只执行运动与开关门二者职责分离。我把数据分成两层轿厢指令Car Calls乘客在轿厢内按下的目标楼层精确到楼层号楼层外呼Hall Calls乘客在某层按上行或下行按钮是“楼层 方向”的二元组调度器做的事情是合并这两类信号生成一个目标序列。电梯本体不需要知道这个序列是怎么来的它只从调度器拿两个值目标楼层和运动方向。这样分层的好处是替换调度算法时电梯类完全不用改。你今天用 LOOK 算法明天想换抢占式调度只需要改调度器的内部逻辑。代码结构上我会用接口把调度策略抽象出来public interface IElevatorScheduler { // 输入轿厢指令和外呼请求输出下一个目标楼层和方向 (int targetFloor, ElevatorDirection direction) NextStop(ElevatorState state, int currentFloor, HashSetint carCalls, ListHallCall hallCalls); }返回值用元组表示“去几楼、往哪个方向”。HallCall是一个(int floor, ElevatorDirection dir)的结构体表示某层有人按了上行或下行。参数说明里有几个值得注意的点state传给调度器是为了让调度器知道电梯当前是否处于开门等待阶段这会影响顺向截停的判断currentFloor用于过滤已经到达的楼层carCalls是去重后的集合因为同一楼层按十次按钮也只算一次。接口设计的核心价值在于可测试。你可以在不启动任何 UI 的情况下构造一组请求断言NextStop的返回值是否符合预期。2.3 等待模型为什么 LOOK 而不是 FCFS先入先出FCFS的思路最简单按请求到达顺序一个个执行。但它会造成两个严重问题电梯来回空跑以及先来的请求可能阻塞后来的同向顺路请求。真实电梯没有用 FCFS 的就是因为效率太差。LOOK 算法是目前实际项目中用得最多的策略。它的规则是电梯保持当前方向运行沿途响应所有同方向的请求直到当前方向上不再有请求再反转方向。与 SCAN 的区别在于SCAN 会一直运行到端站才掉头而 LOOK 在最高请求楼层就掉头省去无意义的空驶。我选择 LOOK 作为模拟实现的默认策略原因有三个逻辑简单状态转移清晰平均等待时间明显优于 FCFS扩展顺向截停、高峰模式等高级特性时不需要推翻重写第三个原因是最关键的。照 LOOK 实现之后你再加入“电梯运行中某层按了同方向请求可即时加入路径”这个特性只需要在请求进入时判断方向是否一致、目标楼层是否在当前运行路径的区间内。以下是 LOOK 调度器的核心选择逻辑// 查找当前方向上最远的请求楼层用于决定是否掉头 private int GetFurthestRequestInDirection( HashSetint carCalls, ListHallCall hallCalls, int currentFloor, ElevatorDirection direction) { var floors new HashSetint(); foreach (var floor in carCalls) floors.Add(floor); foreach (var call in hallCalls) floors.Add(call.Floor); if (direction ElevatorDirection.Up) { var upFloors floors.Where(f f currentFloor).ToList(); return upFloors.Count 0 ? upFloors.Max() : -1; } else { var downFloors floors.Where(f f currentFloor).ToList(); return downFloors.Count 0 ? downFloors.Min() : -1; } }这段代码的思路是先把轿厢指令和外呼合并成一个去重的楼层集合然后在当前方向上找最远的请求。如果返回 -1说明当前方向已经没有请求调度器需要掉头。逻辑说明Where(f f currentFloor)过滤出当前楼层及以上所有请求楼层Max()取其中最远的一层这个值就是 LOOK 算法中决定掉头点的关键依据。参数说明currentFloor参与比较是为了避免电梯把当前楼层重复加进目标序列这在电梯刚到站但该层还有同向请求时尤为关键。3. 用 C# 跑通一个最小可用的电梯模拟从类图到第一趟完整运行3.1 建模电梯、调度器、请求队列三类怎么分现在开始写代码。我遵循“一个类只干一件事”的原则拆出三个核心类Elevator管理楼层计数、速度、状态、方向、当前楼层、开关门计时ElevatorScheduler实现 LOOK 算法维护外部请求队列输出目标楼层SimulationRunner推进时间把调度器的输出交给电梯执行并打印运行日志楼层和门机的物理模拟放在Elevator内部调度完全交给ElevatorScheduler。SimulationRunner只负责“时间 tick 一到调度器给出目标电梯移动一格判断是否到站”。public class Elevator { public int CurrentFloor { get; private set; } public ElevatorState State { get; private set; } public ElevatorDirection Direction { get; private set; } public int TargetFloor { get; set; } public double PositionY { get; private set; } // 连续位置用于模拟加减速 private const double MaxSpeed 2.0; // 最大速度层/秒 private const double Acceleration 0.5; // 加速度层/秒^2 private readonly double _floorHeight; // 每层高度默认 3 米 public Elevator(int initialFloor, double floorHeight 3.0) { CurrentFloor initialFloor; PositionY initialFloor * floorHeight; State ElevatorState.Idle; Direction ElevatorDirection.None; _floorHeight floorHeight; } }PositionY是关键设计它不是按整数楼层跳变的而是连续值。真实电梯的位置是连续变化的楼层只是位置经过的整数点。用连续位置才能正确模拟加减速否则电梯看起来就像瞬间瞬移到位。参数说明里要注意Acceleration 0.5表示每秒钟速度增加 0.5 层MaxSpeed 2.0表示最高每秒跑两层。这些参数直接影响单次运行的耗时也决定了调度器在什么时候应该提前减速。3.2 运行调度最小骨架一个完整的 RUN 循环电梯模拟的主循环本质是一个周期性的 tick 推进器每个 tick 相当于真实系统的一个控制周期。控制周期越短模拟越精细但计算开销越大。我通常把 tick 设为 100 毫秒这样加速、减速、开关门都会有足够的分辨率又不会因为过于精细导致性能问题。public void Tick() { switch (State) { case ElevatorState.Idle: // 从调度器获取下一个任务 var next _scheduler.NextStop( State, CurrentFloor, _carCalls, _hallCalls); if (next.targetFloor -1) return; _carCalls.Remove(next.targetFloor); // 如果目标楼层高于当前楼层方向设为上行 Direction next.targetFloor CurrentFloor ? ElevatorDirection.Up : ElevatorDirection.Down; TargetFloor next.targetFloor; State ElevatorState.Moving; break; case ElevatorState.Moving: MoveTowardTarget(); break; case ElevatorState.DoorOpenWaiting: // 等待计时结束后自动关门 _doorWaitTimer - TickInterval; if (_doorWaitTimer 0) { State ElevatorState.DoorClosing; _doorWaitTimer DoorWaitTime; } break; } }状态机的核心都在这个Tick里。Idle 状态下拿到调度器给的目标楼层后先把该楼层从轿厢指令里移除再设置方向和目标然后进入 Moving。Moving 阶段每次 Tick 移动一段距离到站后状态切换。DoorOpenWaiting 阶段用倒计时控制等待时间时间到就关门。这里有一个容易忽略的参数TargetFloor在电梯运行过程中可能被顺向截停改变。比如电梯正在去 8 楼路上经过 5 楼时 5 楼有人按了同向请求如果按 LOOK 的顺向截停规则目标楼层会更新为 5 楼。这个逻辑需要在 Moving 状态里每 Tick 检查一次而不是只在 Idle 时检查。3.3 顺向截停的实现目标楼层动态更新顺向截停是电梯系统最核心的优化手段也是新手最容易写错的地方。先看规则电梯正在上行去 9 楼5 楼有人按下行按钮——不响应因为方向不一致5 楼有人按上行按钮——响应但要保证电梯还没过 5 楼电梯过了 5 楼才按下——不响应只能等下行回来时再接。关键点是“还没过 5 楼”的判断。很多实现用CurrentFloor 5来判断这在电梯恰好移动了一个整层时没问题但由于位置是连续值电梯可能从 4.7 层直接跳到 5.2 层导致漏判。public bool ShouldStop(int floor, ElevatorDirection dir) { if (dir ! Direction) return false; // 方向不一致不响应 if (Direction ElevatorDirection.Up) { // 目标楼层在当前楼层之上且电梯还未超过该层 return floor CurrentFloor floor TargetFloor; } else { return floor CurrentFloor floor TargetFloor; } }参数说明dir是外部请求的方向floor是请求楼层。floor TargetFloor这个条件保证请求楼层没有被电梯“越过去”。如果在 Moving 状态中检测到ShouldStop返回 true就把TargetFloor更新为该楼层。逻辑说明这里的判断依赖CurrentFloor的整数部分连续位置的影响通过取整来规避。我一般在楼层判断时用(int)Math.Round(PositionY / _floorHeight)而不是直接访问CurrentFloor因为在高速运行时位置可能跨过多层整数楼层跳跃会漏检。顺向截停最好在调度器层面处理而不是在电梯类里。调度器每次被询问时重新检查所有待处理请求看当前方向路径上是否有更近的、同方向的请求有就返回这个更近的楼层。电梯类本身不感知“截停”它只是每 Tick 被调度器告诉新的目标。3.4 第一次完整运行的日志验证状态序列写完骨架后第一件事不是加 UI而是跑一次带日志的命令行模拟。我会打印每个 Tick 的状态变化、方向、当前楼层、目标楼层然后手动验证几个场景。[Tick 20] StateMoving DirUp Floor3 Target9 Speed1.5 [Tick 21] StateMoving DirUp Floor3 Target9 Speed1.8 [Tick 22] StateMoving DirUp Floor4 Target9 Speed2.0 [Tick 23] StateMoving DirUp Floor4 Target5 Speed2.0 - 顺向截停 [Tick 24] StateDecelerating DirUp Floor5 Target5 Speed1.2 [Tick 25] StateDoorOpening DirNone Floor5 Target5看到第 23 Tick 的目标从 9 变成 5就可以确认顺向截停逻辑生效。看到 24 Tick 从 Moving 切到 Decelerating说明到站判断正确。状态打印是整个模拟器最重要的调试工具不要在这一步省事。4. 把调度策略从“能跑”调到“好用”四个关键参数的实测调法4.1 顺向截停与反方向拐点判定边界条件怎么设顺向截停的判定参数直接决定电梯会不会“飞过楼层”。这里最容易翻车的场景是电梯正在下行去 2 楼5 楼有人按下行按钮。按规则应该截停因为方向一致且尚未越过 5 楼。但很多实现因为“当前方向是 Down楼层比较符号写反了”导致电梯直接冲过去。条件说明 - 上行截停区间当前楼层 请求楼层 当前目标楼层 - 下行截停区间当前目标楼层 请求楼层 当前楼层 - 请求方向必须与当前运动方向一致 - 电梯处于 DoorOpenWaiting 时不能参与截停拐点判定是指“什么时候掉头”。LOOK 算法的掉头点不是固定的端站而是当前方向上最远的有效请求。每次到达一个楼层执行完开关门后调度器重新计算当前方向是否还有请求如果没有就掉头。我建议的掉头条件是“当前方向上无任何请求”而不是“当前方向运行到最远请求后再无请求”。这样写可以避免一个经典 bug电梯上行到达 10 楼此时 12 楼又有人按了上行如果按“无请求”判定掉头电梯在 10 楼直接掉头下行而 12 楼的请求只能以后再处理如果按“仍记得之前最远点是 12 楼”的逻辑电梯会继续上行接人。两种策略都有实际场景但后者在连续高负载时表现更好。4.2 门停靠时间、加速度与平均等待时间的关系打开门等待时间是一个典型的权衡参数。等太久乘客体验好但平均等待时间上升太短高峰时段容易夹人。真实电梯通常设 2.5 秒到 5 秒可调。模拟时我用四个档位测试开门等待时间单体平均等待长队清空时间适用场景1.5 秒6.2 秒42 秒高峰快节奏3.0 秒7.8 秒55 秒默认办公5.0 秒9.5 秒68 秒医院/行动不便人群8.0 秒12.3 秒89 秒观光电梯数据说明等待时间每增加 1 秒平均等待大概上升 0.6-0.8 秒长队清空时间上升约 3 秒/层。如果你的模拟结果偏离这个趋势多半是调度逻辑里有额外等待——比如关门后没有立刻启动或者状态切换间有隐性延迟。加速度参数的影响更隐蔽。加速度太小电梯长时间处于低速单次运行时间拉长加速度太大到达目标楼层时速度没来得及减到零就冲过头。我测试的经验公式是从最大速度到零速度的安全制动距离约等于(MaxSpeed^2) / (2 * Deceleration)。如果减速距离大于楼层间距电梯就会越过目标楼层再倒回去这是模拟里最常见的“过站”现象。4.3 冗余楼层停靠与高峰模式开关模拟中容易出现的另一个问题是“冗余停靠”。某层乘客按下行但电梯此时满载且方向相反停靠没有意义。真实电梯会用满载传感器屏蔽所有外呼只响应轿厢指令。模拟时可以通过一个简单参数模拟满载public bool ShouldIgnoreHallCalls(HallCall call) { // 满载时忽略外呼只响应轿厢内指令 if (IsFullLoad) return true; // 外部请求方向与当前方向相反时不响应 if (call.Direction ! Direction) return true; // 电梯处于开门等待阶段不响应新请求 if (State ElevatorState.DoorOpenWaiting) return true; return false; }参数说明里值得留意的是最后一行DoorOpenWaiting阶段不响应新请求很多模拟器漏了这个。真实电梯开关门期间是不接受新外呼的否则会出现门还没关又开的振荡。高频请求时段如果这个状态没处理好电梯会陷入“开门—有人按—重新开门—关门—又有人按”的死循环。高峰模式是一个更高级的开关。真实写字楼早高峰会让所有电梯默认停在一层且下行请求不被响应晚高峰则相反。模拟中只需要改变调度器的请求过滤规则在ShouldIgnoreHallCalls里判断是否处于高峰时段如果是且请求方向与高峰方向相反直接忽略。4.4 用统计指标衡量调度质量而不是肉眼感觉不量化就无法调参。我在每个模拟回合结束时输出三个指标平均等待时间乘客按下按钮到电梯开门的时间均值最长等待时间等待最久的乘客等了多少秒超过 60 秒算调度失败电梯总行驶距离衡量机械损耗和能耗这些指标能直接暴露调度问题。最长等待时间超过阈值通常意味着某个方向的请求被饿死总行驶距离异常增大说明电梯在频繁掉头或空跑。public class SimulationStats { public double AvgWaitingTime { get; set; } public int MaxWaitingTime { get; set; } public double TotalTravelDistance { get; set; } public int FlippedDirectionCount { get; set; } // 掉头次数 }FlippedDirectionCount是非常实用的指标。掉头次数过多说明调度器在“反复纠结”行为类似钟摆。我把掉头次数超过总停靠次数 30% 视为调度质量差需要回看ShouldIgnoreHallCalls的过滤条件或拐点判定逻辑。5. 模拟电梯源码调试中的必踩坑与排查路径5.1 电梯楼层错位到了 5 楼却显示 4 层现象电梯连续运行几个来回后楼层显示和实际请求的楼层差了一两层像是“偷”了一层。原因位置变量PositionY和整数楼层CurrentFloor不同步。CurrentFloor在电梯越过楼层边界时更新但如果位置变量从 4.99 跳到 5.01取整逻辑用了向下取整(int)PositionY / floorHeight5.01 除以 3 后取整变成了 1于是楼层从 4 直接跳成 6中间某个楼层被跳过。另一个原因是楼层编号从 0 开始还是从 1 开始的混乱。解决统一用一个属性获取当前楼层只从PositionY推导public int CurrentFloor (int)Math.Round(PositionY / _floorHeight);每次 Tick 结束后用这个属性重新同步楼层显示。不要维护另一个独立的CurrentFloor变量两个变量的同步逻辑总有遗漏的点。5.2 方向判断在临界楼层上的翻转错误现象电梯从 5 层下行去 2 层经过 3 层时状态偶尔会变成上行日志里出现“楼层 3 → 目标 2方向 Up”的荒谬组合。原因目标楼层与当前位置相等时方向计算用了和而没有处理等于的情况。当电梯位置恰好停在 3.0 层而目标也是 3判断逻辑走入了 else 分支把方向置为 Down下半 tick 又被修正为 Up形成抖动。解决方向判断必须带等于的归零逻辑if (next.targetFloor CurrentFloor) Direction ElevatorDirection.Up; else if (next.targetFloor CurrentFloor) Direction ElevatorDirection.Down; else Direction ElevatorDirection.None; // 已到达不产生方向5.3 持续同向请求导致的饥饿现象长时间模拟后统计表里某个方向的平均等待时间异常高达到 90 秒以上。典型的场景是 1 楼不断有人按上行电梯刚把一个乘客送到 10 楼10 楼又有下行请求于是电梯又掉头下行1 楼的需求一直被“插队”。原因LOOK 算法天生偏向“当前方向的尾部请求”。如果尾部不断有新请求产生电梯会永远往那个方向跑反向请求从饿死。解决加一个最大等待时间旁路。每个外呼请求记录首次按下时间当等待超过设定阈值我一般用 45 秒调度器强制将电梯掉头去接。代码如下// 用于检测是否存在等待超时的请求 var timeoutCall hallCalls .Where(c (now - c.RequestTime) MaxWaitTimeBeforeForcedTurn) .OrderBy(c c.RequestTime) .FirstOrDefault(); if (timeoutCall ! null) { direction timeoutCall.Direction; targetFloor timeoutCall.Floor; return (targetFloor, direction); }5.4 状态转移遗漏开门后忘记关门现象电梯到达楼层开门后日志显示停在 DoorOpenWaiting 状态但倒计时归零后没有切到 DoorClosing电梯一直停在该层后续所有请求都不处理了。原因Timer 或 Tick 逻辑里DoorOpenWaiting的倒计时分支没有正确修改状态或者状态修改放在了某个被 return 提前截断的路径后面。解决把状态转移写成单向、无分支的线性代码每个 tick 只允许发生一次状态转移case ElevatorState.DoorOpenWaiting: _doorWaitTimer - TickInterval; if (_doorWaitTimer 0) { // 只有这一个地方能离开此状态不允许其他分支绕过 State ElevatorState.DoorClosing; _doorCloseTimer DoorCloseTime; } break;排查时先在DoorClosing的入口打日志确认每次DoorOpenWaiting都能进入关门流程。如果日志缺失问题百分之百在倒计时分支被提前 return 了。5.5 浮点累积误差导致的位置漂移现象长时间运行后电梯停在 1 层但PositionY是 3.0000001下一次启动上行时CurrentFloor变成了 3跳过了 2 层。原因多次加减速运算中浮点数反复累加产生了微小误差。楼层判断函数对 3.0000001 向下取整为 3而不是 1。解决楼层判断前先做四舍五入另一个更可靠的方法是用整数毫米代替浮点层数// 位置以毫米为单位存储避免浮点噪声 private int _positionMm; // 每层 3000mm public int CurrentFloor (int)Math.Round(_positionMm / 3000.0); // 每 Tick 移动距离也以毫米计算 int deltaMm (int)(speed * 3000.0 * TickInterval); _positionMm directionSign * deltaMm;用整数毫米后浮点误差被完全消除。这是我在几次实战中验证过的做法强烈推荐给所有追求长时间稳定运行的模拟器。根据实际需求项目中可以直接用 double 类型跑短时间的演示。但如果模拟时间超过 10 分钟浮点漂移必然出现提前防御不要赌它不发生。6. 从模拟到验证把调度器抽出来做回调测试模拟器能跑通只是第一步要验证调度质量你需要一个不依赖 UI、不依赖真实时间的测试框架。我习惯把调度器做成一个纯逻辑类时间由外部注入。外部模拟时间向前推进时回调OnHallCall和OnCarCall注入请求然后每 tick 调用Tick()最后断言电梯服务的楼层序列是否符合预期。核心技巧是替换时间源。不直接调用Thread.Sleep而是维护一个虚拟时间变量每 Tick 手动加 100 毫秒。这样测试可以瞬间跑完 10 分钟模拟还能精确复现某个 tick 的异常。我保留了两个最小覆盖测试集单方向多个请求验证各楼层按顺序到达双向交替请求验证掉头行为正确[TestMethod] public void LookScheduler_ShouldService_RequestsInDirection() { var scheduler new LookScheduler(); scheduler.SetCarCall(8); scheduler.SetCarCall(5); scheduler.SetHallCall(6, ElevatorDirection.Up); var stops new Listint(); // 模拟过程省略只保留核心断言 Assert.AreEqual(new[] { 5, 6, 8 }, stops); }这个测试断言的核心并非调度器的具体实现而是行为约定方向一致的请求按从近到远的顺序依次服务。只要行为符合文档约定替换成任何新型调度算法时都能用同一套测试验证。断言里输出 5、6、8 的顺序5 楼的轿厢指令、6 楼的外呼同向请求、8 楼的远目标依次完成。做完这些建议留一个“可视化运行日志 统计指标输出”的习惯每次改动调度参数后记录平均等待时间、掉头次数、总行驶距离三个数值。我自己的血泪经验是只改一个参数然后对比前后两个日志发现数值变化方向与预期相反通常不是参数本身的问题而是某个状态转移在参数变化后被触发了不同的分支路径。这时候把状态机转移日志打出来往往比反复调参更快定位。调试调度器时的另一条习惯是“先锁定算法再调参数”。参数乱改只能让结果更不可解释。遇到异常结果先检查请求集是否构造合理确认上层数据无恙再回来调调度参数。希望这个方向能帮到你把 C# 模拟电梯从“能跑”做到“可信”。本文还有配套的精品资源点击获取