尧图精选

C#实现电梯模拟控制系统:状态机与多线程调度实战

🕒 发布时间:2026/9/8 3:27:29 📁 来源:尧图网络
简介这一C#模拟电梯控制源码面向具备基础语法、希望进阶面向对象与多线程开发的初中级学习者演示如何将电梯、楼层、乘客抽象为对象并通过委托事件响应楼层按钮、用状态机管理升降与开关门、结合调度策略优化停靠顺序。压缩包共27个文件包括cs源代码、可直接运行的exe、承载界面文字的resx、窗体图标ico与调试所需的pdb等类型整体仅246KB结构精简便于逐文件分析。目前已有280人学习浏览。项目自带Windows窗体界面可直观观察电梯运行状态与楼层信息同时涉及异常处理、单元测试及Visual Studio调试技巧配合资源描述中的十个知识点逐一对照适合作为C#小型系统设计与实战演练的参考范例。1. 项目概述与核心需求解析1.1 为什么选择C#来写电梯模拟系统电梯控制系统在众多工业控制场景里算是很有代表性的一个。它既有明确的状态流转运行、停止、开门、关门又有严格的安全逻辑超载不开门、运行中不开门、急停优先还要处理大量并发请求多个楼层的人同时按按钮非常适合用来练习面向对象设计、状态机建模和多线程调度。我选C#来落地这个模拟系统主要看重三个点第一C#的事件驱动模型天然契合电梯场景。电梯里每个按钮按下去本质上就是一个事件触发C#的event关键字配合委托能把“乘客按楼层按钮”和“电梯响应请求”解耦得非常干净。第二C#的async/await异步编程模型处理多楼层请求、模拟电梯运行时间轴时代码可读性比传统多线程模型好太多。你不用手动管理Thread和Lock用async方法配合CancellationToken就能实现平滑的电梯调度。第三配合WPF或WinForms做上位机界面C#几乎是效率最高的选择。实时刷新电梯当前楼层、运行方向、内外呼按钮状态用数据绑定加属性通知就能做得很顺手。1.2 这套源码解决了什么问题说白了这套“C# 模拟电梯控制源码”解决的是这样一个问题在没有真实电梯硬件的情况下用代码完整模拟一套电梯控制系统。它模拟的范围包括多楼层多电梯的调度逻辑单梯和群控电梯都可以扩展电梯运行的状态机静止、加速、匀速、减速、平层、开门、关门内呼轿厢内选层和外呼楼层上下行请求的响应与取消超载报警、急停、开门延时等异常场景通过UI界面实时显示电梯位置、运行方向、当前载重状态对学习者来说这套代码的价值在于你不需要一台真实的电梯做实验就能把计算机操作系统里的“生产者-消费者”“读者-写者”这类经典并发问题放在一个看得见、摸得着的业务场景里吃透。对面试准备来说电梯调度算法也是C#面试题尤其是和“多线程”“状态机”“算法优化”挂钩时的常客。2. 内容整体设计与思路拆解2.1 从功能需求倒推代码架构我在设计这套源码时没有直接上手写类而是先从电梯的实际行为倒推拆解。真实世界中的电梯用户能感知到的东西就三类电梯门开、关、正在开关过程中电梯位置在第几层往上还是往下电梯的响应我按了按钮电梯有没有理我正往这边来因此我抽象出了三个核心类Elevator电梯本身、ElevatorController调度控制器、FloorButtonPanel楼层外呼按钮面板再加上一个用于承载界面数据的ElevatorViewModel。这里有一个很关键的设计决策让Elevator类完全不感知UI的存在。它只负责状态变化和逻辑处理对外暴露事件如DoorOpened、FloorReached由WPF界面去订阅这些事件并刷新UI。这样做的直接好处是当你以后想把这套代码迁移成真实设备的上位机程序只要把UI订阅部分换成PLC通讯或者硬件指令下发核心调度逻辑一行都不用改。2.2 调度算法的取舍LOOK算法电梯调度算法有多种先来先服务FCFS、最短寻道优先SSTF、SCAN电梯算法单向扫到底再回头、LOOK扫描方向上有请求才继续走等。我最终选了LOOK算法作为核心调度逻辑。原因是它在效率和公平性之间最平衡而且代码实现也不复杂。LOOK算法的核心思想是电梯维护一个当前方向向上或向下当电梯向上运行时只响应上方楼层的请求并且按由低到高的顺序依次停靠当上方没有请求了就改变方向向下响应下方楼层的请求有个例外如果当前方向顺路且在电梯前方的请求优先响应反方向的请求需要等电梯掉头后再处理用生活类比来解释这就好比这台电梯比较“执着”它认定往上走就先把上面的活儿干完绝不在半路掉头回去接楼下的人除非上方区域没有任务了。这保证了电梯不会因为频繁换向导致“饿死”某个极端楼层的请求。2.3 为什么用状态机而不是if-else堆逻辑电梯系统最容易写崩的地方就是那种层层嵌套的if-else判断如果门开着且没有超载且到达目标楼层则关门否则等待……这种代码过不了两周你自己都看不懂。我在这里引入了状态模式。电梯核心状态分为Idle待机停在某层门关着MovingUp向上运行中MovingDown向下运行中DoorOpening正在开门模拟到开门到位需要1秒DoorOpen门已完全打开等待乘客DoorClosing正在关门DoorClosed门已关闭每种状态对应一个处理类负责处理“当前状态下能接受哪些外部请求”。比如DoorOpen状态下你不能立刻让电梯运行必须先关门MovingUp状态下如果你按了同方向前方楼层的按钮可以顺路响应反向按钮则只记录不响应。这么做最大的收益是把每个状态独立的逻辑内聚在对应的类里新增状态时不需要改动原有状态逻辑符合开闭原则。调试时也能一眼看出电梯卡在哪个状态问题定位速度快得多。3. 核心细节解析与实操要点3.1 核心代码结构一览先看项目目录划分这是我推荐的解决方案结构ElevatorSimulator/ ├── Models/ │ ├── Elevator.cs │ ├── ElevatorState.cs │ ├── ElevatorRequest.cs │ └── Enums.cs ├── Controllers/ │ ├── ElevatorController.cs │ └── SchedulingAlgorithm.cs ├── ViewModels/ │ └── ElevatorViewModel.cs ├── Views/ │ ├── MainWindow.xaml │ └── MainWindow.xaml.cs └── Helpers/ └── RelayCommand.cs这个结构的核心思想是按职责分层。Models放数据实体和核心状态Controllers放调度逻辑ViewModels负责界面数据和UI解耦Views只是纯展示层。3.2 电梯核心状态类电梯本身的核心数据结构如下这是整个系统的枢纽public class Elevator { // 电梯编号 public int Id { get; set; } // 当前楼层以1为起始楼层 public int CurrentFloor { get; private set; } // 当前运行方向 public Direction Direction { get; private set; } // 电梯当前状态 public ElevatorState State { get; private set; } // 内部请求队列乘客按了哪个楼层 public Listint InternalRequests { get; private set; } // 是否超载 public bool IsOverloaded { get; private set; } // 是否处于急停状态 public bool IsEmergencyStopped { get; private set; } // 门开度百分比0-100用于UI显示 public int DoorOpenPercent { get; private set; } // 事件到达某层 public event EventHandlerFloorReachedEventArgs? FloorReached; // 事件门状态变化 public event EventHandlerDoorStateChangedEventArgs? DoorStateChanged; // 事件超载状态变化 public event EventHandlerbool? OverloadStateChanged; public Elevator(int id, int initialFloor 1) { Id id; CurrentFloor initialFloor; Direction Direction.None; State ElevatorState.Idle; InternalRequests new Listint(); IsOverloaded false; IsEmergencyStopped false; DoorOpenPercent 0; } // 状态流转方法会在调度器中调用 }这里有一个容易被新手忽略的设计细节所有公开属性中能只读的尽量只读外部只能通过方法调用改变状态。比如CurrentFloor和State的setter都是private外部想改变电梯位置只能通过调度器触发这能有效防止代码中到处随意修改电梯状态导致的逻辑混乱。3.3 调度的核心数据结构和决策调度器是系统大脑它需要维护每部电梯的内部请求去哪个楼层每个楼层的上行请求要上楼的人在几层每个楼层的下行请求要下楼的人在几层对应代码如下public class ElevatorController { private ListElevator _elevators; private Dictionaryint, bool _upRequests; // 楼层 - 是否有上行请求 private Dictionaryint, bool _downRequests; // 楼层 - 是否有下行请求 private SchedulingAlgorithm _algorithm; private bool _running; public ElevatorController(int elevatorCount, int floorCount, SchedulingAlgorithm algorithm) { _elevators new ListElevator(); _upRequests new Dictionaryint, bool(); _downRequests new Dictionaryint, bool(); for (int i 0; i floorCount; i) { _upRequests[i 1] false; _downRequests[i 1] false; } for (int i 0; i elevatorCount; i) { _elevators.Add(new Elevator(i 1)); } _algorithm algorithm; _running false; } // 外部按钮按下后调用 public void OnFloorButtonPressed(int floor, Direction direction) { if (direction Direction.Up) _upRequests[floor] true; else if (direction Direction.Down) _downRequests[floor] true; // 触发调度选择电梯去响应 DispatchElevator(floor, direction); } // 内呼按钮按下后调用 public void OnInternalButtonPressed(int elevatorId, int targetFloor) { var elevator _elevators.FirstOrDefault(e e.Id elevatorId); if (elevator null) return; if (!elevator.InternalRequests.Contains(targetFloor)) { elevator.InternalRequests.Add(targetFloor); elevator.InternalRequests.Sort(); } } }这里有几个实用的技术点要说明。按楼层优先的bool字典比List接口更简单我见过很多人用Listint去存每层的请求判断时用Contains遍历。楼层数量不多一般不超过20层用Dictionaryint, bool能做到O(1)查询复杂度而且天然防止重复存储。按钮事件的触发源要区分物理按钮和“虚拟按钮”真实电梯中外呼按钮按完会亮灯电梯响应后会熄灭。模拟中要处理好这个状态映射我通常在UI按钮的Tag里存楼层号方向的元组点击时直接传给调度器。4. 实操过程与核心环节实现4.1 LOOK算法完整实现这一段是整个源码最核心的部分我直接放出可用代码然后逐步解释为什么这么写。public class LookAlgorithm : SchedulingAlgorithm { public override bool HasNextStop(Elevator elevator, ElevatorController controller) { // 如果内部请求或外部请求都没有就待机 return GetNextStop(elevator, controller) ! -1; } public override int GetNextStop(Elevator elevator, ElevatorController controller) { int currentFloor elevator.CurrentFloor; bool movingUp elevator.Direction Direction.Up; bool movingDown elevator.Direction Direction.Down; // 第一步处理内部请求优先响应 int internalStop FindNextInternalRequest(elevator, currentFloor, movingUp, movingDown); if (internalStop ! -1) return internalStop; // 第二步处理外部请求 int externalStop FindNextExternalRequest(controller, currentFloor, movingUp, movingDown); if (externalStop ! -1) return externalStop; return -1; } private int FindNextInternalRequest(Elevator elevator, int currentFloor, bool movingUp, bool movingDown) { if (movingUp) { // 向上找第一个大于当前楼层的内部请求 var upMatches elevator.InternalRequests .Where(f f currentFloor) .OrderBy(f f); if (upMatches.Any()) return upMatches.First(); // 上方没有请求了但当前方向仍有下行内部请求 // LOOK算法此时应该改变方向 var downMatches elevator.InternalRequests .Where(f f currentFloor) .OrderByDescending(f f); if (downMatches.Any()) return downMatches.First(); } else if (movingDown) { // 向下找第一个小于当前楼层的内部请求 var downMatches elevator.InternalRequests .Where(f f currentFloor) .OrderByDescending(f f); if (downMatches.Any()) return downMatches.First(); var upMatches elevator.InternalRequests .Where(f f currentFloor) .OrderBy(f f); if (upMatches.Any()) return upMatches.First(); } else { // 待机状态找最近的请求楼层 if (elevator.InternalRequests.Count 0) return -1; return elevator.InternalRequests .OrderBy(f Math.Abs(f - currentFloor)) .First(); } return -1; } private int FindNextExternalRequest(ElevatorController controller, int currentFloor, bool movingUp, bool movingDown) { if (movingUp) { // 向上方向找大于当前楼层的外部请求优先顺序是从下往上 for (int floor currentFloor 1; floor controller.FloorCount; floor) { if (controller.HasRequest(floor, Direction.Up) || controller.HasRequest(floor, Direction.Down)) return floor; } // 上方没有请求则掉头找下方请求 for (int floor currentFloor - 1; floor 1; floor--) { if (controller.HasRequest(floor, Direction.Up) || controller.HasRequest(floor, Direction.Down)) return floor; } } else if (movingDown) { // 向下方向找小于当前楼层的外部请求 for (int floor currentFloor - 1; floor 1; floor--) { if (controller.HasRequest(floor, Direction.Up) || controller.HasRequest(floor, Direction.Down)) return floor; } // 下方没有请求则掉头找上方请求 for (int floor currentFloor 1; floor controller.FloorCount; floor) { if (controller.HasRequest(floor, Direction.Up) || controller.HasRequest(floor, Direction.Down)) return floor; } } else { // 待机状态找离当前楼层最近的外部请求 int bestFloor -1; int bestDistance int.MaxValue; for (int floor 1; floor controller.FloorCount; floor) { if (controller.HasRequest(floor, Direction.Up) || controller.HasRequest(floor, Direction.Down)) { int distance Math.Abs(floor - currentFloor); if (distance bestDistance) { bestDistance distance; bestFloor floor; } } } return bestFloor; } return -1; } }这段代码里有几个细节值得单独讲讲。为什么内部请求优先于外部请求这是模拟真实电梯的一个重要逻辑。轿厢内的乘客已经站在电梯里了系统必须优先把他们送到目的地而楼层外的呼梯用户只是“在等”不存在被困在轿厢里的时间压力。搜索热词里出现“c# 怎么检测变量数值变化”对应到电梯系统就是FloorReached事件这是通过属性变更通知实现的而不是轮询检测。WPF下用INotifyPropertyChanged接口设置CurrentFloor时触发事件调度界面就能实时刷新。为什么外部请求查找时不管是上行还是下行按钮都按楼层距离来处理真实电梯的一个特性是当电梯向上运行经过某层时如果这一层有人在等电梯不管他要去楼上还是楼下电梯都会停一下开门让乘客进入车厢后再通过内呼按钮处理目标方向。所以我在查找外部请求时不区分Direction.Up还是Direction.Down只要该层有请求就响应。真正的方向适配发生在乘客进入电梯后通过内部按钮表达需求。4.2 电梯运行的核心驱动逻辑调度器设定好目标楼层后Elevator类里要有一个逐层运行的推进逻辑。我用的是一个定时器驱动模型模拟电梯每0.5秒走一层的节奏。public async Task MoveToFloorAsync(CancellationToken token) { while (!token.IsCancellationRequested) { // 没有任何目标楼层进入待机 if (InternalRequests.Count 0) { State ElevatorState.Idle; return; } int nextFloor _algorithm.GetNextStop(this, _controller); if (nextFloor -1) { State ElevatorState.Idle; Direction Direction.None; return; } // 目标楼层和当前楼层相等说明当前层就有请求直接开门 if (nextFloor CurrentFloor) { InternalRequests.Remove(CurrentFloor); _controller.ClearRequest(CurrentFloor); await OpenDoorAsync(token); } else if (nextFloor CurrentFloor) { Direction Direction.Up; State ElevatorState.MovingUp; await Task.Delay(500, token); // 模拟运行一层的时间 CurrentFloor; FloorReached?.Invoke(this, new FloorReachedEventArgs(CurrentFloor)); } else { Direction Direction.Down; State ElevatorState.MovingDown; await Task.Delay(500, token); CurrentFloor--; FloorReached?.Invoke(this, new FloorReachedEventArgs(CurrentFloor)); } } }核心要点是电梯每次只处理一个楼层的目标到了就重新从调度器获取下一个目标。这种“当前目标导向”的好处在于随时新增请求都不会干扰电梯的主循环逻辑新请求只需要被调度器“看到”就行。另一个要留意的细节是运行方向切换的时机。代码中我用了最简单的策略GetNextStop计算出目标楼层然后根据目标楼层和当前楼层的对比确定下一步方向不对电梯“应在何时掉头”做预判。这样做的结果是电梯的行为完全由请求分布决定虽然对某些极端请求序列不够“聪明”但代码清晰度和可调试性更好。4.3 事件驱动的UI刷新WPF界面刷新方面我没有用定时器轮询UI而是让Elevator类在关键节点触发事件UI层订阅后更新。以刷新电梯当前楼层为例public partial class MainWindow : Window { private ElevatorController _controller; private CancellationTokenSource _cts; private void OnFloorReached(object sender, FloorReachedEventArgs e) { Dispatcher.Invoke(() { // 更新电梯在UI上的Y坐标位置或更新楼层指示文本 UpdateElevatorPosition(e.Floor); }); } private void UpdateElevatorPosition(int floor) { // 计算电梯轿厢在画布上的Y位置 double canvasHeight ElevatorCanvas.ActualHeight; double floorHeight canvasHeight / _controller.FloorCount; double yPosition canvasHeight - floor * floorHeight; ElevatorRectangle.Margin new Thickness(0, yPosition, 0, 0); CurrentFloorText.Text $当前楼层{floor}; } }这里有个线程模型的经验事件的触发可能来自后台任务async方法体但UI只能从主线程操作。所以要用Dispatcher.Invoke把刷新操作调度回UI线程。也可以用Dispatcher.BeginInvoke区别是Invoke是同步等待BeginInvoke是异步投递。UI刷新场景下如果没有中间态的严格顺序要求BeginInvoke通常手感更流畅。再补一个让空白地方也充实起来的编码知识点集成INotifyPropertyChanged的地方建议都用CallerMemberName自动填充属性名字免得手动写字符串出错。代码如下public class ObservableObject : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string? propertyName null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetPropertyT(ref T field, T value, [CallerMemberName] string? propertyName null) { if (EqualityComparerT.Default.Equals(field, value)) return false; field value; OnPropertyChanged(propertyName); return true; } }这个写法配合CurrentFloor等属性代码量可以缩减三分之一。5. 常见问题与排查技巧实录5.1 UI冻结问题现象电梯运行过程中界面卡住不动任务管理器显示主线程忙。原因最常见的做法是把电梯移动逻辑用while(true)循环Thread.Sleep(500)写在了UI线程里。Thread.Sleep会让UI线程完全阻塞界面当然卡住。解决把电梯运行逻辑放进async Task用await Task.Delay(500)替代Thread.Sleep。// 错误写法 private void MoveElevator() { while (true) { Thread.Sleep(500); // 更新界面 } } // 正确写法 private async Task MoveElevatorAsync(CancellationToken token) { while (!token.IsCancellationRequested) { await Task.Delay(500, token); // 更新界面 } }await Task.Delay在异步方法中会自动回到调用上下文默认是UI线程同步上下文所以接下来的代码可以在UI线程上安全执行界面不会卡。5.2 按钮请求未正确清除现象电梯到达楼层开门停靠后按钮灯还亮着下次路过还会再停一次。原因请求清除逻辑遗漏。电梯到达某层后不仅要移除内部请求InternalRequests还要清除外部请求_upRequests[floor]和_downRequests[floor]。解决电梯每次平层后统一走一个ClearRequest流程public void ClearRequest(int floor) { InternalRequests.Remove(floor); _upRequests[floor] false; _downRequests[floor] false; }注意调ClearRequest时不能盲目清空因为可能同一楼层有两个方向都有人等所以要用bool值标记两个方向分别判断。5.3 多电梯调度不均衡现象两台电梯永远只有一台在跑另一台闲着。原因派梯策略写得太简单比如每次来请求都选Idle状态且楼层最近的电梯这样在楼层边界附近的电梯会被反复选中另一台永远没有机会。解决给每台电梯加一个“忙碌度”评价函数综合考虑距离和待完成任务数private int ScoreElevator(Elevator e, int floor) { int distanceCost Math.Abs(e.CurrentFloor - floor) * 10; int taskCost e.InternalRequests.Count * 5; int stateCost e.State ElevatorState.Idle ? 0 : 2; return distanceCost taskCost stateCost; } public void DispatchElevator(int floor, Direction direction) { var bestElevator _elevators .Where(e !e.IsEmergencyStopped !e.IsOverloaded) .OrderBy(e ScoreElevator(e, floor)) .First(); bestElevator.AddExternalRequest(floor, direction); }这只是一个启发式策略没有标准答案。实际项目中你可能会根据高峰期的流向调整权重比如早高峰时让下行任务权重更高。5.4 异步任务已取消但状态没恢复现象按了“急停”按钮后又恢复电梯界面状态很乱不知道当前在哪层。原因取消CancellationToken时如果正在Task.Delay等待会直接抛出OperationCanceledException代码如果没捕获这个异常后面的状态恢复逻辑就不会执行。解决在取消流程中增加异常管理try { await MoveToFloorAsync(cts.Token); } catch (OperationCanceledException) { // 取消时记录当前楼层 Log($电梯 {elevator.Id} 在楼层 {elevator.CurrentFloor} 被急停); } finally { // 无论正常结束还是被取消都要恢复状态 elevator.State ElevatorState.Idle; elevator.Direction Direction.None; }这里有个面试官常问的细节OperationCanceledException不是普通业务异常逻辑上它是“操作中止”的信号所以不要用空catch吞掉至少要日志记录或状态标注。5.5 快速连续按按钮导致内存膨胀现象疯狂点击外呼按钮程序内存持续增长。原因每次按钮点击都会创建一个Task去执行移动逻辑旧的Task如果还没有被取消就仍然持有引用GC无法回收。解决确认每台电梯只有一个长期运行的调度任务按钮点击只修改共享请求集合不新建Taskprivate void StartElevatorLoop(Elevator elevator) { // 确保每台电梯只启动一个后台任务 if (_elevatorTasks.ContainsKey(elevator.Id)) return; var cts new CancellationTokenSource(); _elevatorTasks[elevator.Id] cts; Task.Run(() ElevatorLoopAsync(elevator, cts.Token), cts.Token); }结合热词里出现的“c# 上位机开发”和“c# tcp连接数量多”这两个方向不少读者会在想这个模拟电梯控制和真实工业上位机有什么关系我最初在设计这个模拟器时也有同样的思考。首先是控制协议的复用。真实电梯控制柜通常走Modbus、OPC UA或厂商私有协议上位机需要完成的工作本质上是“把界面操作翻译成协议报文”。我在模拟器中把所有按钮操作都封装成了统一指令接口比如PressUpButton(floor)、SelectFloor(elevatorId, floor)这样后续即使要对接真实设备也只需要实现一个协议适配器UI层和调度层完全不受影响。其次是心跳监测逻辑。真实设备通讯时有掉线检测的需求模拟器中体现在“检测电梯是否卡死”每次FloorReached事件都会更新时间戳控制器定时检查这个时间戳如果长时间没更新就报警。这是工业通讯里最基本的心跳机制C#中实现有点类似于Socket通讯里的KeepAlive。6. 扩展方向与个人体会6.1 从单梯到群控这套源码默认是单梯调度改造成多梯群控只需要在调度器里做两件事维护多台Elevator实例派梯时用评价函数类似的ScoreElevator选一台最优电梯群控的难点不在于单梯逻辑而在于如何让多台电梯之间协作协调。一个常见的策略是“分区”一楼以上的电梯服务高层靠近地面的电梯服务低层中间层根据请求动态分配。这个扩展方向对想做C#高级编程的读者很有价值涉及了分布式调度、负载均衡等思路在单机内的简化实现。6.2 引入Unity 3D可视化仿真再进一步还可以把WPF界面换成Unity 3D引擎把电梯、楼层、乘客建模为3D场景。这个方向有几个好处能直观表现电梯加速度、减速度、平层精度的物理效果模拟乘客上下电梯的动画之后可以把“电梯内人数变化”和“超载检测”串联成完整闭环做毕业设计或项目展示时视觉冲击力强很多。Unity C#的组合本身也是游戏开发的主流栈做这套模拟器等于一个知识复用。6.3 个人踩坑记录最后分享一个我在这套代码中曾经踩过的坑给大家提个醒千万别在Elevator类的属性set中直接写业务逻辑。我最早写代码时图省事直接在CurrentFloor的setter里调FloorReached?.Invoke。后来调试时发现某些操作比如初始化、人为拖拽界面上的电梯图块也会触发FloorReached事件导致调度器误认为电梯到站打开了一堆不该开的门。排查了半天最后决定把所有事件触发点收口到MoveToFloorAsync中只有电梯真正因为移动到达楼层时才触发事件。这是一个典型的“图一时方便付三倍代价”的案例。状态变化的事实属性被赋值和状态变化的“业务含义”电梯移动到了新楼层应当响应请求是两回事必须区分对待。对于想用这套源码练手C#的读者我的建议是先跑通再重构。第一遍不要想着优化性能就让电梯老老实实一层层走把状态流转跑对第二遍加入异常场景超载、急停、关门夹人检测第三遍再去讨论群控调度和算法优化。三遍走下来你对C#的异步、事件、状态机的理解绝对会上一个层次。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →