C#上位机工业级封装:通信重试+全局异常+结构化日志(设备监控实战)
大家好我是威哥。前两年做过一个汽配厂的设备监控项目一开始图快代码写得很“糙”通信断了就报错异常没捕获直接崩日志只有一行“出错了”现场排查问题要翻几小时文本。后来痛定思痛花了一周把代码重构了一遍加上了工业级的通信重试、全局异常兜底和结构化日志之后项目稳得一批现场工人再也没凌晨给我打过电话。今天把这套封装方案分享出来都是能直接抄的干货。一、先说说工业现场的“三大痛点”做工业上位机最头疼的就是这三件事通信不稳定车间网线被叉车碰、无线Wi-Fi信号弱PLC/传感器经常断连没重试机制的话数据直接丢。异常没兜底一个小异常比如解析数据越界就能让整个程序崩生产线停一分钟损失几千块。日志难排查普通文本日志只有时间和错误信息没有设备ID、通信地址这些上下文出问题要翻几万行日志头都大了。所以这次的封装目标很明确通信断了自动重连/重试不用人工干预。任何异常都不崩程序记录日志后尝试恢复。日志结构化、带上下文出问题5分钟内定位到原因。二、核心封装1通信重试断路器Polly是神器工业现场通信重试不能用简单的while(true)循环要考虑重试策略指数退避比如第一次等1秒第二次等2秒第三次等4秒避免网络拥堵时一直重试。断路器模式连续失败10次就“跳闸”暂停通信5分钟避免把PLC/传感器“冲死”。不同异常不同处理比如“连接超时”要重试“地址无效”就不用重试是配置问题。这里我用了Polly.NET生态最火的重试/熔断库配合S7netplus和FluentModbus封装了一个通用的通信服务。2.1 安装PollyInstall-Package Polly Install-Package Polly.Extensions.Http2.2 封装S7-1200通信服务带重试断路器直接上代码重点看注释里的踩坑点usingPolly;usingPolly.CircuitBreaker;usingS7.Net;usingSystem.Net.Sockets;publicclassS7PlcService{privatereadonlyILoggerS7PlcService_logger;privatePlc_plc;privatereadonlystring_ip;privatereadonlyint_rack;privatereadonlyint_slot;// 重试策略处理SocketException和PlcException指数退避最多重试5次privatereadonlyAsyncPolicy_retryPolicy;// 断路器策略连续失败10次跳闸暂停5分钟privatereadonlyAsyncCircuitBreakerPolicy_circuitBreakerPolicy;publicS7PlcService(ILoggerS7PlcServicelogger,stringip,intrack0,intslot1){_loggerlogger;_ipip;_rackrack;_slotslot;_plcnewPlc(CpuType.S71200,ip,rack,slot);// 1. 配置重试策略_retryPolicyPolicy.HandleSocketException()// 处理网络异常.OrPlcException(exex.ErrorCode!ErrorCode.WrongVarFormat)// 处理PLC异常但排除“地址无效”这种配置错误.WaitAndRetryAsync(retryCount:5,sleepDurationProvider:attemptTimeSpan.FromSeconds(Math.Pow(2,attempt)),// 指数退避1s, 2s, 4s, 8s, 16sonRetry:(ex,ts,attempt,ctx){_logger.LogWarning(ex,PLC通信失败{Attempt}次重试等待{Ts}秒后重试IP{Ip},attempt,ts.TotalSeconds,_ip);});// 2. 配置断路器策略_circuitBreakerPolicyPolicy.HandleSocketException().OrPlcException().CircuitBreakerAsync(exceptionsAllowedBeforeBreaking:10,// 连续10次失败跳闸durationOfBreak:TimeSpan.FromMinutes(5),// 暂停5分钟onBreak:(ex,ts){_logger.LogError(ex,PLC通信连续失败断路器跳闸暂停{Ts}分钟IP{Ip},ts.TotalMinutes,_ip);},onReset:(){_logger.LogInformation(PLC通信恢复断路器重置IP{Ip},_ip);});}// 连接PLC带重试断路器publicasyncTaskConnectAsync(CancellationTokenstoppingToken){await_retryPolicy.ExecuteAsync(async(){await_circuitBreakerPolicy.ExecuteAsync(async(){if(!_plc.IsConnected){_logger.LogInformation(正在连接PLCIP{Ip},_ip);await_plc.OpenAsync();_logger.LogInformation(✅ PLC连接成功IP{Ip},_ip);}});});}// 读取DB块带重试断路器publicasyncTaskbyte[]ReadBytesAsync(DataTypedataType,intdb,intstartByte,intcount,CancellationTokenstoppingToken){returnawait_retryPolicy.ExecuteAsync(async(){returnawait_circuitBreakerPolicy.ExecuteAsync(async(){if(!_plc.IsConnected)awaitConnectAsync(stoppingToken);_logger.LogDebug(读取PLC数据类型{DataType}DB{Db}起始地址{StartByte}长度{Count}IP{Ip},dataType,db,startByte,count,_ip);returnawait_plc.ReadBytesAsync(dataType,db,startByte,count);});});}// 写入数据带重试断路器省略和读取类似publicasyncTaskWriteAsync(stringaddress,objectvalue,CancellationTokenstoppingToken){...}publicvoidDispose(){if(_plc.IsConnected)_plc.Close();_plc.Dispose();}}踩坑点总结不要无限重试最多5-10次不然网络拥堵时会雪上加霜。断路器很重要连续失败后暂停避免把PLC/传感器“冲死”工业设备通信端口处理能力有限。区分异常类型配置错误比如地址写错不用重试直接报错让人工处理。三、核心封装2全局异常处理程序永远不崩工业上位机最忌讳的就是“一报错就崩”哪怕是一个小的解析异常也可能导致生产线停。所以我们要做全局异常兜底捕获所有未处理的异常记录日志后尝试恢复。3.1 Worker Service后台服务的全局异常处理Worker Service是工业上位机常用的后台服务模式全局异常处理要覆盖这三个地方ExecuteAsync里的异常用try/catch包裹。AppDomain.CurrentDomain.UnhandledException非托管异常。TaskScheduler.UnobservedTaskException未观察到的Task异常。直接上代码publicclassProgram{publicstaticasyncTaskMain(string[]args){// 1. 先配置Serilog后面会讲Log.LoggernewLoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().WriteTo.File(logs/device-monitor-.txt,rollingInterval:RollingInterval.Day).CreateLogger();try{Log.Information( 设备监控服务启动 );// 2. 注册全局异常处理AppDomain.CurrentDomain.UnhandledException(sender,e){Log.Fatal((Exception)e.ExceptionObject,捕获到非托管全局异常服务即将重启);// 这里可以加代码记录日志后自动重启服务比如用systemd或任务计划程序};TaskScheduler.UnobservedTaskException(sender,e){Log.Error(e.Exception,捕获到未观察到的Task异常);e.SetObserved();// 标记为已观察避免程序崩};// 3. 启动HostvarhostHost.CreateDefaultBuilder(args).UseSerilog()// 用Serilog替换默认日志.ConfigureServices((context,services){// 注册S7PlcService单例services.AddSingletonS7PlcService(spnewS7PlcService(sp.GetRequiredServiceILoggerS7PlcService(),192.168.1.100));// 注册Workerservices.AddHostedServiceDeviceMonitorWorker();}).Build();awaithost.RunAsync();}catch(Exceptionex){Log.Fatal(ex,服务启动失败);}finally{Log.CloseAndFlush();}}}// Worker里的ExecuteAsync也要用try/catch包裹publicclassDeviceMonitorWorker:BackgroundService{privatereadonlyS7PlcService_plcService;privatereadonlyILoggerDeviceMonitorWorker_logger;publicDeviceMonitorWorker(S7PlcServiceplcService,ILoggerDeviceMonitorWorkerlogger){_plcServiceplcService;_loggerlogger;}protectedoverrideasyncTaskExecuteAsync(CancellationTokenstoppingToken){while(!stoppingToken.IsCancellationRequested){try{// 正常的业务逻辑读取PLC数据、解析、上传vardataawait_plcService.ReadBytesAsync(DataType.DataBlock,1,0,20,stoppingToken);_logger.LogInformation(读取到PLC数据{Data},BitConverter.ToString(data));}catch(Exceptionex){// 捕获Worker里的所有异常记录日志后继续循环不要崩_logger.LogError(ex,Worker业务逻辑异常继续运行);}awaitTask.Delay(1000,stoppingToken);}}}3.2 WinForms/WPF的全局异常处理如果是WinForms/WPF界面程序还要加这两个// WinForms的Program.cs里[STAThread]staticvoidMain(){Application.SetHighDpiMode(HighDpiMode.SystemAware);Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);// 注册UI线程异常Application.ThreadException(sender,e){Log.Fatal(e.Exception,捕获到UI线程异常);MessageBox.Show($程序发生异常{e.Exception.Message}\n请联系管理员,错误,MessageBoxButtons.OK,MessageBoxIcon.Error);};// 注册非UI线程异常AppDomain.CurrentDomain.UnhandledException(sender,e){Log.Fatal((Exception)e.ExceptionObject,捕获到非UI线程全局异常);MessageBox.Show($程序发生严重异常{((Exception)e.ExceptionObject).Message}\n程序即将退出,严重错误,MessageBoxButtons.OK,MessageBoxIcon.Stop);};Application.Run(newMainForm());}踩坑点总结Worker里的循环一定要用try/catch包裹不然一个异常就会让Worker退出服务虽然没崩但业务停了。TaskScheduler.UnobservedTaskException一定要加很多异步异常没被观察到会直接导致程序崩。异常后不要直接退出记录日志后尝试恢复比如重连PLC、继续循环工业现场“能跑就行”。四、核心封装3结构化日志Serilog上下文信息普通的文本日志是这样的2026-02-20 10:00:00 读取PLC数据 2026-02-20 10:00:01 出错了出问题根本不知道是哪个设备、哪个地址出的错。结构化日志是这样的JSON格式{Timestamp:2026-02-20T10:00:00.123456708:00,Level:Error,MessageTemplate:PLC通信失败IP{Ip}, 地址{Address},Properties:{Ip:192.168.1.100,Address:DB1.DBW0,Exception:System.Net.Sockets.SocketException: 连接超时...}}有了上下文信息出问题5分钟内就能定位到原因。这里我用了Serilog.NET生态最火的结构化日志库。4.1 安装SerilogInstall-Package Serilog.AspNetCore Install-Package Serilog.Sinks.Console Install-Package Serilog.Sinks.File Install-Package Serilog.Sinks.Elasticsearch // 可选输出到Elasticsearch方便后续分析4.2 配置Serilog工业现场版在Program.cs里配置Serilog重点是按日期分文件每天一个日志文件方便归档。保留30天日志避免日志占满磁盘。输出JSON格式结构化方便分析。加上下文信息比如设备ID、环境开发/生产。Log.LoggernewLoggerConfiguration().MinimumLevel.Debug().MinimumLevel.Override(Microsoft,LogEventLevel.Information)// 微软的日志设为Info减少噪音.Enrich.FromLogContext()// 允许从LogContext添加上下文.Enrich.WithProperty(DeviceId,Device-Monitor-001)// 全局上下文设备ID.Enrich.WithProperty(Environment,Production)// 全局上下文环境// 输出到控制台开发时用.WriteTo.Console(outputTemplate:[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception})// 输出到文件生产时用按日期分保留30天JSON格式.WriteTo.File(path:logs/device-monitor-.json,rollingInterval:RollingInterval.Day,retainedFileCountLimit:30,formatter:newSerilog.Formatting.Json.JsonFormatter())// 可选输出到Elasticsearch后续用Kibana分析// .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri(http://192.168.1.200:9200))// {// AutoRegisterTemplate true,// IndexFormat device-monitor-logs-{0:yyyy.MM.dd}// }).CreateLogger();4.3 在代码里使用Serilog加上下文在业务代码里用LogContext添加临时上下文比如当前的PLC地址、传感器IDusing(LogContext.PushProperty(PlcAddress,DB1.DBW0))using(LogContext.PushProperty(SensorId,Sensor-001)){_logger.LogInformation(开始读取传感器数据);try{vardataawait_plcService.ReadBytesAsync(DataType.DataBlock,1,0,2,stoppingToken);_logger.LogInformation(读取成功数据{Data},BitConverter.ToString(data));}catch(Exceptionex){_logger.LogError(ex,读取失败);}}这样日志里就会带上PlcAddress和SensorId出问题直接定位到是哪个传感器、哪个地址。踩坑点总结日志级别要合理Debug开发调试、Info正常业务、Warning通信波动、Error异常、Fatal严重错误不要全用Info。日志不要太啰嗦正常业务只记录关键信息比如“读取成功”不要记录每一步细节不然日志量太大。一定要加全局上下文比如设备ID、环境后续分析日志时能区分是哪个设备出的问题。五、实战整合设备监控项目跑起来把上面三个模块整合起来一个工业级的设备监控项目就完成了通信层S7PlcService带重试断路器通信稳定。业务层DeviceMonitorWorker带try/catch异常不崩。日志层Serilog结构化带上下文排查问题快。部署后的效果通信稳定性之前每天断连10次现在断连后自动重连工人根本感觉不到。程序稳定性之前每周崩2次现在连续运行3个月没崩过。排查问题效率之前翻几小时日志现在5分钟内定位到原因。六、总结工业级封装的“三大原则”最后总结几个能直接落地的经验稳字当头工业上位机不需要花里胡哨的功能稳定是第一位的重试、异常兜底、日志都是为了“稳”。不要重复造轮子Polly、Serilog这些库都是经过千锤百炼的直接用不要自己写重试逻辑、自己写日志框架。日志是排查问题的关键一定要结构化、带上下文不然出问题哭都来不及。异常要兜底任何异常都不要让程序崩记录日志后尝试恢复工业现场“能跑就行”。如果大家还有工业级封装的问题或者需要完整的项目代码欢迎在评论区交流。后续我打算讲讲怎么把这套封装和OPC UA结合起来做更通用的设备监控到时候再写文章分享。

相关新闻

.NET9 WPF上位机核心优化:3D渲染+虚拟化布局+无闪烁刷新(连杆机加工线3D监控实战)

.NET9 WPF上位机核心优化:3D渲染+虚拟化布局+无闪烁刷新(连杆机加工线3D监控实战)

大家好,我是威哥。上个月把连杆精镗孔的ML.NET优化搞定后,厂长又提了个“可视化升级”的需求:“威哥,现在的趋势图太干巴了,能不能做个3D的机加工线监控?能看到每台机床的实时状态、刀具的磨损动画、工件的…

2026/5/17 5:41:51 阅读更多 →
从人口数据看国家竞争力:规模红利尚未自动转化为创新优势

从人口数据看国家竞争力:规模红利尚未自动转化为创新优势

一、一组值得深思的数据对比 在分析全球主要经济体的发展潜力时,人口始终是基础性变量。根据最新统计数据: 美国:约3.42亿人(2026年估算)日本:约1.22亿人(2026年估算)美日合计&…

2026/7/2 20:44:51 阅读更多 →
为什么LinkedIn简历匹配很糟糕

为什么LinkedIn简历匹配很糟糕

我最近从零开始构建了一个职位-候选人匹配系统。这不是玩具。这是一个生产系统,接收真实的简历,并将它们与数万个真实的职位发布进行匹配。这种系统必须运作良好,才能影响人们的职业发展。 在我构建它之前,我和其他人一样。我会打…

2026/7/4 16:18:25 阅读更多 →

最新新闻

Subliminal进阶:模拟复杂用户交互和系统对话框的完整指南

Subliminal进阶:模拟复杂用户交互和系统对话框的完整指南

Subliminal进阶:模拟复杂用户交互和系统对话框的完整指南 【免费下载链接】Subliminal An understated approach to iOS integration testing. 项目地址: https://gitcode.com/gh_mirrors/subl/Subliminal Subliminal是一个强大的iOS集成测试框架&#xff0c…

2026/7/5 17:05:07 阅读更多 →
Android分布式架构深度解析:基于空间架构模式的终极实践指南

Android分布式架构深度解析:基于空间架构模式的终极实践指南

Android分布式架构深度解析:基于空间架构模式的终极实践指南 【免费下载链接】android-tech-frontier 【停止维护】一个定期翻译国外Android优质的技术、开源库、软件架构设计、测试等文章的开源项目 项目地址: https://gitcode.com/gh_mirrors/an/android-tech-f…

2026/7/5 17:05:07 阅读更多 →
一套方案跑通三大平台:YOLO全场景部署实战指南,附一键环境配置脚本

一套方案跑通三大平台:YOLO全场景部署实战指南,附一键环境配置脚本

做工业视觉落地的同行应该都有同感:训模型只是第一步,部署才是磨死人的开始。同一份YOLO权重,既要跑Windows产线上位机,又要部署Linux后台服务器,还要塞进Jetson边缘盒子,每个平台环境依赖不一样、推理引擎…

2026/7/5 17:03:07 阅读更多 →
MarkItDown:如何用Python统一处理数十种文档格式

MarkItDown:如何用Python统一处理数十种文档格式

MarkItDown:如何用Python统一处理数十种文档格式 【免费下载链接】markitdown Python tool for converting files and office documents to Markdown. 项目地址: https://gitcode.com/GitHub_Trending/ma/markitdown 想象一下这样的场景:你的桌面…

2026/7/5 17:03:07 阅读更多 →
NVC多平台部署指南:Linux、macOS和Windows下的安装与配置

NVC多平台部署指南:Linux、macOS和Windows下的安装与配置

NVC多平台部署指南:Linux、macOS和Windows下的安装与配置 【免费下载链接】nvc VHDL compiler and simulator 项目地址: https://gitcode.com/gh_mirrors/nv/nvc NVC是一款开源的VHDL编译器和模拟器,支持VHDL-2008标准并具有出色的模拟性能。本指…

2026/7/5 17:03:07 阅读更多 →
3步掌握MinerU:构建智能文档解析系统的实战指南

3步掌握MinerU:构建智能文档解析系统的实战指南

3步掌握MinerU:构建智能文档解析系统的实战指南 【免费下载链接】MinerU Transforms complex documents like PDFs and Office docs into LLM-ready markdown/JSON for your Agentic workflows. 项目地址: https://gitcode.com/GitHub_Trending/mi/MinerU Mi…

2026/7/5 17:03:07 阅读更多 →

日新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/5 0:03:34 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/5 0:03:34 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/5 0:07:38 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/5 0:03:34 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/5 0:03:34 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/5 0:07:38 阅读更多 →

月新闻