1. 项目全景:这块看板到底要做什么
做工业现场这块C# WPF大数据电子看板之前,我一直在想一个问题:工厂老板和车间主任,真正想在大屏幕上看到什么?
答案其实很朴素。设备开没开、今天产量多少、良品率多少、哪台机器报警了、哪个产线停滞了。这些数据平时散落在PLC里、MES系统里、Excel表格里,甚至是老师傅的脑子里。电子看板的核心价值,就是把这些零散数据汇集到一个屏幕上,让车间里所有人一抬头就能掌握全局状态。
这篇文章我想结合自己做过的一个源码项目,把C# WPF电子看板的完整技术链路拆开讲透。从设备数据采集、大数据量的UI刷新、MVVM架构落地,到现场部署的坑,全程都是用真实项目说话。适合正在做上位机、工业数据可视化,或者想用WPF搭一套生产数据平台的开发者参考。源码层面的思路和关键代码我会贴出来,你可以直接抄作业改到自己项目里。
1.1 智慧工厂电子看板的需求边界
先别急着写代码,需求边界理清楚,项目就成功了一半。我在拿到需求时习惯问客户几个问题:看板放在哪里、屏幕多大、刷新频率要求多少、要展示哪些数据类型、数据从哪来、断网了怎么办。这些问题直接决定技术选型和架构设计。
以我做的这个项目为例,车间部署了一块2.5米宽的LED拼接屏,刷新频率要求不高,3到5秒刷新一批数据就够,因为人眼在远距离看大屏时,太快的闪烁反而看不清。核心数据包括:各产线设备运行状态(运行/停机/故障)、当日产量实时累计、良品率趋势曲线、设备综合效率(OEE)、当前报警列表、过去24小时每小时产量柱状图。数据来源主要是西门子S7-1200系列PLC,通过以太网模块接入车间局域网,另外还有一部分手工录入的数据和从数据库读取的历史数据。
这里有个典型的认知误区:很多人一提“大数据”就想到海量数据存储和高并发计算,但工厂电子看板的大数据更多是“多源、异构、实时”的数据汇集。你要处理的不是亿级数据量,而是几十个点位、每秒几帧的状态变化,加上历史趋势分析。真正有挑战的是如何让数据采集稳定、UI刷新流畅、长时间运行不卡死、断线能自动重连。明白了这一点,你就不会把架构设计得过度复杂,比如一上来就上微服务、消息队列,那是给自己挖坑。
1.2 为什么选C# WPF而不是Web前端或Winform
这个项目选型的时候,我也对比过几种方案:Web前端(Vue+ECharts)、Winform、WPF、甚至Unity做3D可视化。最后锁定WPF,是综合了开发效率、生态成熟度和现场表现力的结果。
WPF相比Winform,最核心的优势在于数据绑定和界面样式分离。Winform做这种看板,你要手动控制每个Label和TextBox的Text属性,数据一多代码就乱成一锅粥。WPF则天然支持MVVM模式,你在ViewModel里改属性,界面通过绑定自动更新,代码结构清晰很多,后期加需求也好维护。
相比Web前端方案,WPF没有浏览器安全沙箱的限制,可以直接访问串口、走TCP协议、调用OPC客户端库,这对工业设备通信来说是绝对优势。Web方案要对接PLC,还得自己封装WebSocket服务或者写中间件转发,多了一层链路就多一个故障点。而且WPF基于DirectX渲染引擎,矢量图形和动画性能不错,做曲线、柱状图、仪表盘这类可视化组件很流畅。项目里我用LiveCharts做实时折线图,刷新几百个数据点时CPU占用也就百分之几。
另外一点实战经验是:工业现场的电脑配置通常不高,很多还是老旧的工控机。WPF的渲染是GPU加速的,在低配置机器上表现明显比Winform的GDI+绘制要平滑。你要是追求酷炫效果,还可以用自定义控件模板做深色科技感主题,比传统Winform好看太多。当然WPF也有学习曲线,尤其是绑定、线程模型、依赖属性这些概念,新手要花一些时间去理解,但一旦跨过这个门槛,做这类看板项目效率是非常高的。
1.3 数据采集链路与系统架构设计
我习惯把整套看板系统分成四层,每一层职责单一,互相通过接口通信。最底层是设备层,就是车间里的PLC、传感器、扫码枪;往上走是采集层,用独立的后台服务去轮询或者订阅设备数据;再往上走是数据处理层,负责把原始数据清洗、聚合、计算成指标;最上层就是WPF客户端,负责展示和交互。
这里有个关键设计决策:采集层必须独立于WPF界面运行。我见过很多项目直接在窗体的后台线程里采集数据,界面一卡数据就丢,两者互相拖累。我的做法是把采集服务放在独立进程里,通过局域网把数据推送回看板客户端。当然,如果项目规模小、设备点位少,你也可以用同一个进程里的后台服务,核心是要把采集逻辑和UI逻辑彻底解耦。
具体到通信协议,西门子PLC我一般用S7netplus库走TCP直接读写DB块,简单直接。如果你现场有OPC服务器,比如Kepware,那就用OPC DA或OPC UA接口去订阅,好处是设备接入更规范,坏处是多一层中间件要维护。串口设备就用System.IO.Ports.SerialPort类,设置好端口号和波特率就行。数据采集上来之后先统一转成标准的数据模型,包括设备ID、时间戳、指标值、质量戳,再交给上层处理。这个标准化过程非常关键,不然以后每接一种设备就要改一遍上层代码,维护成本会失控。
1.4 MVVM架构在看板项目里的落地思路
MVVM模式理论讲起来一套一套的,落在WPF看板项目里,我总结下来就三件事:数据放哪、界面怎么拿数据、操作怎么触发。
具体来说,每个看板页面对应一个ViewModel类,ViewModel里放的是所有要显示的数据属性。这个类要继承INotifyPropertyChanged接口,属性值一变就通知界面刷新。界面层通过绑定表达式如{Binding CurrentOutput},把TextBlock或DataGrid的某个属性绑到ViewModel的属性上。用户操作或后台事件需要触发动作时,用ICommand接口包装成命令,而不是直接在事件处理里写业务逻辑。
这个项目里我做了三个核心ViewModel:设备状态ViewModel管理所有设备的开关机状态和报警信息;产量统计ViewModel管理产量、良品率等聚合指标;趋势图表ViewModel管理历史曲线数据。每个ViewModel都可以独立写单元测试,因为不依赖界面控件。调试阶段这个优势尤其明显——不用启动界面,光调数据和逻辑就能省一半时间。提醒一点:在ViewModel里不要用Dispatcher直接操作控件,这是个常见的坏习惯,会让MVVM架构形同虚设。矩阵数据更新用ObservableCollection,让集合本身通知界面,而不是手动刷新整个List。
2. 核心技术点逐个击破
架构搭好之后,项目里真正难啃的是那些具体的技术细节。我按自己踩坑的经验,把最关键的几个点单独拿出来说:设备数据采集、大数据量的实时刷新、图表可视化和看板布局。
2.1 设备数据采集:连接PLC和OPC的实战写法
设备通信是看板系统最容易翻车的环节。刚开始做这个项目时,我用S7netplus读西门子PLC的DB块,遇到几个坑:连接不稳定、DB偏移地址搞错、多线程并发读导致PLC通讯负载过高。后来总结出一套相对稳妥的写法,核心是建立“连接池 + 独立读取线程 + 断线重连”机制。
先看S7netplus读PLC的基本写法,这是很多上位机项目的起点:
using S7.Net; // 创建PLC连接对象 Plc plc = new Plc(CpuType.S71200, "192.168.0.1", 0, 1); plc.Open(); // 打开连接 // 读取DB1.DBW0,即DB块第1个字,偏移量0,长度2字节 object value = plc.Read("DB1.DBW0"); ushort realValue = Convert.ToUInt16(value); plc.Close();代码本身很简单,但实际运用中连接不能频繁开关,我一般启动时建立连接,然后用一个独立的读取线程循环读。循环里设置Thread.Sleep(500)之类的时间间隔,根据点位数量和刷新要求调整。读取到的值直接封装成数据模型放进事件里抛出去,不直接在采集线程里更新UI。
如果你现场用的是OPC服务器,现在行业规范在往OPC UA迁移。我用的OPCFoundation的OPCUaClient库,连接和订阅的写法大概是这样的框架:先创建ApplicationConfiguration,配置好证书和端点,然后创建Session会话,订阅对应的节点。OPC UA的好处是数据模型更完整,带时间戳和质量戳,而且跨平台。但配置复杂度也更高,证书、安全策略这些会让新手头大。我的建议是:项目点数少用S7netplus直连PLC更省事;点数多、设备厂家杂,用OPC UA统一接入更划算。
2.2 大数据量下的UI刷新策略
这是看板项目最核心的性能关卡。我做过测压测试:当设备点位到100个以上,每个点3秒刷新一次,如果用很粗暴的方式直接更新绑定的属性,界面会明显掉帧卡顿。原因很简单,每次属性变化都会触发WPF的绑定时更新,界面渲染开销会随数据量线性增长。
解决办法有三个,我在项目里同时用了。
第一个是降低UI刷新频率。数据采集可以2秒一次,界面刷新设成5秒一次。这中间加一个数据缓冲层,每次都把最新数据存到内存里,定时器到点了再一次性推给界面。人眼对5秒级别的更新差异几乎无感,CPU占用却能降一半。代码上就是维护一个ConcurrentDictionary存最新值,再加个DispatcherTimer定时把快照推给ViewModel。
第二个是开启DataGrid的UI虚拟化。如果你的看板里有实时数据表格,几百行数据全量渲染会卡。WPF的DataGrid默认启用虚拟化,但你要注意别在DataGrid外面套了StackPanel之类会破坏虚拟化的容器。还有一点,不要对行数据频繁做样式重新计算,比如根据某列值变化改整行背景色,这个操作会强制所有可见行重新测量,极其消耗性能。
第三个是最小化通知范围。如果你有一百个数据点要刷新,不要用一个很大的对象整体通知界面刷新,而是拆成细粒度的属性,让每个属性单独通知。我在项目里验证过,细粒度通知比粗粒度通知性能高不少,渲染时间能减少约40%。另外界面上不需要实时显示的数据,比如历史曲线只显示最近一小时,那老数据就缓存到列表里,不要继续绑到界面集合上,避免集合无限增长导致内存持续膨胀。
2.3 实时曲线与KPI大数字的显示方案
看板最吸引眼球的是大数字和实时曲线。大数字我用TextBlock加超大字号,配一个渐变背景或光晕效果,科技感一下就出来了。但要注意的是:大数字不要用TextBlock默认绑定,因为数字每秒在变,频繁格式化字符串也会有开销。我习惯在ViewModel里直接做好格式化,比如绑定一个OutputText属性,它返回的是已经格式化成“12345.6 件”的字符串,界面只管显示。
实时曲线我用LiveCharts组件。老版本LiveCharts.Wpf在刷新高频数据时会有一点闪烁问题,新版LiveCharts2好很多,支持增量更新。核心写法是在图表绑定的Series集合里维护一个ChartValues集合,收到新数据就Add进去,同时RemoveAt(0)移除过期数据点,保持曲线窗口长度固定。实测在150个数据点的折线图上,每秒刷新一次,CPU占用在5%以下,流畅度完全可以接受。
柱状图用于展示过去24小时每小时产量,这类静态图表刷新频率低,用普通的ObservableCollection绑定就行。需要多说一句,图表的坐标轴颜色、网格线颜色、图例字体这些尽量做成全局样式资源,项目里十几个图表统一风格,后期想换主题改一处就全生效,不用一个个图表去调。
2.4 看板界面布局与多屏显示适配
车间里的LED大屏通常是一个超宽比例,比如16:4.5或者32:9,这和普通电脑显示器的16:9差别很大。布局设计时不能简单套用普通Dashboard模板,否则两侧会大片留白。我做的这个项目,主界面是Grid分区布局,分成上下两块。上半部分左侧是设备状态列表,中间是产量KPI大数字和OEE仪表盘,右侧是报警信息滚动列表。下半部分是两个图表区域,左边放24小时产量柱状图,右边放实时趋势曲线。
WPF做这种自适应布局,核心工具就是Grid的星号比例分配。ColumnDefinition的Width设置为2*、1*这样的比例值,窗口缩放了各区按比例伸缩,不会乱。另外为了保证在大屏和小屏上都显示正常,我用Viewbox做局部缩放,让关键图表区域随着屏幕尺寸等比缩放,但文字大小和间距不会失真。
有一个实用技巧:看板程序通常要全屏运行,我在启动参数里加了一个-autostart选项,检测到该参数就直接调用WindowState=Maximized和WindowStyle=None进入无边框全屏模式。现场维护人员不用每次去按全屏键。同时监控鼠标是否空闲,长时间无操作就自动全屏,防止有人误触窗口栏切出去了。
3. 源码核心模块实操拆解
说完了设计思路,我直接把项目源码里最能复用的几个模块拆给大家看。这一节偏代码实践,我会把关键结构、类之间的关系和重要的逻辑处理都标出来,方便你对照着改到自己的项目里。
3.1 设备数据采集服务的模块结构
我的采集服务面向多种设备,所以抽象了一个IDeviceDataSource接口,这样以后接新设备只需增加实现类,不用改上层逻辑。接口的定义很简单,核心就是启动停止和事件通知:
public interface IDeviceDataSource { event EventHandler<DeviceDataEventArgs> DataReceived; void Start(); void Stop(); } public class DeviceDataEventArgs : EventArgs { public string DeviceId { get; set; } public DateTime Timestamp { get; set; } public Dictionary<string, object> Values { get; set; } public int Quality { get; set; } // 数据质量戳,0为无效,1为有效 }以S7-1200 PLC采集为例,我实现了PlcDataSource类。类内部维护一个Plc连接实例、一个读取线程标志位,以及点位配置列表。配置写在JSON文件里,包括PLC的IP、机架号、槽号、要读的数据块地址列表。启动时先加载配置文件再逐个创建连接并开始读取线程。这里有个关键设计:连接失败不能直接抛异常退出程序,而要做重试,重试间隔递增,从1秒到30秒封顶。工业现场网络闪断太常见了,程序必须能自动恢复。
同时我实现了TcpListenerDataSource用来对接自定义协议设备,比如某些嵌入式控制器通过TCP主动往看板推送数据。这个模块类似一个迷你服务器,监听固定端口,接收客户端连接,每个客户端开一个独立Task处理数据帧。数据帧我自定义了一个很简单的协议:帧头、设备ID、长度、JSON数据体、校验。这样对接其他系统时非常灵活,消息直接序列化成JSON。
3.2 串口设备接入的参数配置与容错
串口设备在工厂里仍然大量存在,比如地磅、条码枪、老款仪表。接入串口最核心的就是参数要对,否则收到的全是乱码或收不到数据。我用System.IO.Ports.SerialPort,关键参数包括端口号、波特率、数据位、停止位、校验位。常见的仪表默认配置是9600波特率、8个数据位、1个停止位、无校验,但不同厂家可能不同,凡是现场联调时必须确认。
串口通信有个容易忽略的坑:数据接收是分批到达的。比如设备发一条完整的指令,长度可能有50个字节,但串口接收缓冲区可能分三次才收齐。如果收到一次就解析一次,必然会出现半包、粘包问题。我习惯的做法是建立一个接收缓冲区,把收到的数据追加进去,然后按帧头帧尾或结束符截取完整帧再解析,剩余数据留在缓冲区等下一次到达。这个“分包凑帧”的逻辑,是串口通信稳定可靠的关键。
3.3 WPF绑定层与ViewModel设计
界面层我按功能拆分了多个ViewModel,公共基类封装了属性通知和命令创建的样板代码。基类的实现非常简单,就是大家常见的ObservableObject:
public class ObservableObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer<T>.Default.Equals(field, value)) return false; field = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); return true; } }有了这个基类后,写一个产量显示属性就非常简洁:
public class ProductionViewModel : ObservableObject { private double _currentOutput; public double CurrentOutput { get => _currentOutput; set => SetProperty(ref _currentOutput, value); } private string _outputText; public string OutputText { get => _outputText; set => SetProperty(ref _outputText, value); } }这里我特意放了一个OutputText格式化属性。从采集服务拿到原始数值后,先用统一方法计算成带单位的显示文本,再赋值给OutputText。界面上的TextBlock绑定的是OutputText而不是CurrentOutput,避免在XAML里频繁用StringFormat格式化大数字,性能更好也更容易做国际化。
ViewModel里的数据从哪里来?我在项目里创建了一个DataService类,它内部持有多个IDeviceDataSource的实例。采集事件到达后,DataService把数据分发给对应的ViewModel。分发过程在后台线程执行,而ViewModel的属性更新会自动通过WPF的绑定引擎封送到UI线程。这里有个WPF的经典知识点要强调:当你在后台线程修改实现了INotifyPropertyChanged的属性时,绑定引擎会帮助你自动跨线程更新界面,不需要手动Dispatcher.BeginInvoke。不过ObservableCollection在后台线程直接Add偶尔会踩到线程冲突的雷,所以集合更新我统一用的ConcurrentQueue做缓冲,定期批量搬到UI集合。
3.4 主看板界面布局与数据模板
主界面布局文件我用的XAML,核心是一个Grid,分成两大区块,分成6列5行。上半区是状态面板,下半区是图表区域。下面这段代码是我布局的精简片段,为了保持篇幅只体现结构核心:
<Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="1.2*"/> <ColumnDefinition Width="2*"/> <ColumnDefinition Width="1.2*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="2*"/> <RowDefinition Height="1*"/> </Grid.RowDefinitions> <!-- 左侧设备状态列表 --> <ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding DeviceStatusList}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch"> <Ellipse Width="12" Height="12" Fill="{Binding StatusBrush}"/> <TextBlock Text="{Binding DeviceName}" Margin="10,0"/> <TextBlock Text="{Binding StatusText}" Foreground="{Binding StatusBrush}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <!-- 中间KPI区域 --> <UniformGrid Grid.Row="0" Grid.Column="1" Rows="2" Columns="2"> <TextBlock Text="{Binding OutputText}" FontSize="42" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/> <!-- 其他KPI数字 --> </UniformGrid> </Grid>这个布局重点说明一下:ListBox绑定了DeviceStatusList,每个项的模板是个日程表,左边状态指示灯用Ellipse,颜色绑定StatusBrush属性。设备停机时StatusBrush是红色,运行是绿色,故障是黄色闪烁。颜色本身是从ViewModel返回的Brush对象而不是字符串,虽然这样ViewModel里引用了WPF的类型,但在看板这种纯客户端场景下可以接受,换来的是模板写起来简单直接。
为了避免DataGrid和ListBox集合项过多导致性能下降,我只在界面上展示当前所有设备的即时状态,比如50台设备的列表,50项而已,完全没压力。历史报警记录我只加载最近200条,并加上“近1小时”“近24小时”筛选,避免一次性加载几千条再渲染,界面卡顿而且没必要。
4. 实战中的坑与排查技巧
这一节是我最想分享的部分。开发环境里代码写得好好的,一到车间现场就各种幺蛾子。我把自己在项目调试和部署中碰到的问题整理成一份速查表,后面每个问题再详细说说排查思路。
4.1 UI卡顿与数据刷新频率怎么平衡
症状很典型:看板运行时界面鼠标移动迟缓,KPI数字跳变有延迟感,甚至出现窗口撕裂。用任务管理器看CPU占用,WPF进程直接吃满一个核心。
我排查后定位到了根因:采集线程每500毫秒拿到一批数据,直接把DataService里100多个属性全部赋值了一遍。虽然绑定是细粒度的,但界面渲染管线每一帧要处理上百次属性变化通知,再加上图表集合同时追加数据点,主线程根本忙不过来。另外图表控件的默认动画也消耗不少CPU,LiveCharts在每次数据变化时都会播放过渡动画,高频更新时这就是性能黑洞。
解决方案有三个,按效果排序:
第一,采集线程和UI刷新线程分离,UI用5000毫秒定时器从缓冲快照取数,一次只刷新一帧。第二,关闭图表动画,或在大于10个数据点更新时自动关闭动画,只有初始化时才播放动画。第三,对KPI大文本使用TextBlock而不是Label,并且避免使用DropShadowEffect这类GPU密集型效果,改用简单叠加的Border实现发光感。
我在项目里加了性能监控面板,显示当前帧率和UI线程负载。现场调优时不用猜,直接看数据就能定位瓶颈。这个面板只有调试模式显示,发布版本自动隐藏。
4.2 设备断线重连与半包粘包问题
工业现场最常见的故障就是网线松动、交换机重启、设备固件崩溃。S7netplus在连接断开后,如果你还在执行Read,会抛异常。不加处理,程序这个采集线程就会死掉,界面数据永远停在最后那一刻,而且不会恢复。我后来写了ConnectionMonitor类,专门负责连接状态检查,定期Ping设备IP,发现不通就切到重连模式。
重连机制我用的指数退避策略:第一次重连等待1秒,第二次2秒,第三次4秒,最多30秒封顶。这个策略比固定5秒重连靠谱得多,不会在设备重启期间疯狂打连接请求把设备彻底卡死。连接恢复后再重新订阅或开始轮询,同时丢弃缓冲区内过期的数据,避免把历史旧数据推送上去造成界面数值跳变。
TCP分包粘包问题,我一开始也没处理好。设备推送的原始字节流是一串连续的,比如三帧数据一次性到达,或者一帧数据被拆成两次。如果按固定字节数截取就会错位。后来我采用“开始符+长度+包体+结束符”的帧协议,解析时按长度字段拿到整帧字节,再用CRC校验确认无误才交给上层。这样就算数据流错乱,也能在下一次找到正确的帧头重新同步。
4.3 ObservableCollection过多更新引发的内存与性能问题
ObservableCollection每个元素的增删都会触发CollectionChanged事件,如果这个集合绑定了DataGrid并且每秒钟增删几十条,界面渲染的开销会非常夸张。我在做历史报警列表时踩过这个坑,滚动列表一直抖动,CPU还居高不下。
解决方式有两个。第一个是尽量做分页或按窗口加载,比如只保留最近500条,超出就RemoveAt(0)。但RemoveAt(0)在ObservableCollection里也有成本,因为它是基于List实现的,移除头部元素会引发整体移动。所以对于高频追加的列表,我用的是批量重建方案:数据积累到缓冲,到了刷新周期一次性构建新的List再赋给可观察集合的整体。
如果一定要用ObservableCollection做高频逐条更新,那就实现一个批量通知类,或者使用BindingList并开启RaiseListChangedEvents为false自己控制通知时机。我在生产环境验证下来,批量重建列表比逐条Add和Remove性能高一倍以上,而且代码逻辑更好理解。
4.4 现场部署与运行环境适配问题
车间电脑普遍配置低、系统老旧,有的甚至还是Windows 7。WPF在.NET Framework 4.7.2下运行是最稳妥的,但新版C#语法和依赖库可能要求更高版本。我做项目时目标框架选的是.NET Framework 4.8,因为Windows 10自带,不用额外装运行库。如果用了.NET Core 3.1或.NET 6,发布时自包含模式带上运行库,虽然exe体积大了几十MB,但现场机器裸奔也能跑。
另一个容易忽视的是DPI缩放问题。车间电脑屏幕分辨率可能很高,但系统缩放设置是125%或150%。如果你的WPF程序没有适配DPI,在大屏上显示会很模糊,文字发虚。我处理方法是:程序启动时手动设置ProcessDPIAware,并使用WPF的Per-Monitor DPI支持,每个窗口根据所在显示器的DPI动态调整字体和布局。尤其是在LED拼接屏场景,其驱动输出的分辨率和信号分辨率不一定一致,可能导致画面整体偏移。
杀毒软件和防火墙拦截也是个大坑。有一次现场部署后设备数据一直收不到,排查了半天发现是Windows防火墙把WPF程序的TCP监听端口拦了。一般我部署时在安装说明里写明要添加防火墙入站规则、允许程序通过。如果客户用的是360之类的第三方杀毒软件,还要提醒他们把程序目录加入白名单,不然后台服务可能在半夜被静默杀掉,第二天一早看板数据全部冻结。
4.5 看板程序长时间运行的稳定性
电子看板是7x24小时不间断运行的,这就对内存管理和资源释放提出很高要求。我遇到过最典型的问题:程序运行三天后内存占用从200MB涨到1.2GB,最终系统卡死。用内存分析工具抓下来一看,是事件订阅泄漏和图表数据集合没有清空导致的。
事件订阅泄漏真的很隐蔽。比如设备服务类订阅了DataReceived事件,但窗体关闭或热切换页面时没退订,服务对象被窗体引用着无法释放,每次切换页面就泄漏一份。后来我统一用弱事件模式,或者在所有订阅处都严格保证成对出现,并且窗体关闭时主动调用Cleanup方法解除订阅。
另外一个问题是图表控件长期运行时内部缓存堆积。LiveCharts2虽然比老版本做得好,但数据点累计过多还是会卡。我的做法是定时清理,每个图表最多保留2000个数据点,超出就把最早的点批量移除,而不是逐个RemoveAt。移除时界面刷新也会有一次性能抖动,配合前面说的批量重建方式就能缓解。实际应用中,我还加了一个“超长运行自检”的定时器,每6小时检查一次内存占用,如果超过阈值就记录日志并自动重启应用,这是在现场保证稳定的最后一道保险。
我在实际调试这套源码时,最大的体会是:电子看板的难点并不在于某个单独技术有多深,而是所有环节必须环环相扣。采集要稳定不丢数,传输要即时不延迟,界面要流畅不卡顿,长时间运行要可靠不出错,任何一环掉了链子,看板就变成一块昂贵的装饰屏。这套项目源码的思路基本覆盖了从数据采集到展示的全链路,我后续还打算在这个基础上扩展移动端看板,通过WebSocket把关键数据推送到手机和平板上,让车间主任不在大屏前也能收到产线报警。如果你正在做类似的项目,建议先把基础链路跑通,再逐步加功能,不要一上来就想把所有设备协议都接一遍。