大数跨境
0
0

C# 工业监控系统 MVP 架构 + 实时数据 + 异常处理全方案

C# 工业监控系统 MVP 架构 + 实时数据 + 异常处理全方案 DotNet技术匠
2025-11-17
0
导读:C# 工业监控系统开发实战:基于 MVP 架构的高效实现。

前言

工业4.0时代,工业监控系统开发需求激增,但传统WinForm开发中界面逻辑、业务逻辑与数据访问高度耦合,导致代码维护困难、测试覆盖率低。

MVP(Model-View-Presenter)架构模式通过分离职责、解耦组件,为C#开发提供更清晰的代码结构和更易维护的解决方案

本文以工业设备监控系统为例,详细解析MVP架构的设计与实现,涵盖实时数据更新、异步操作及异常处理等核心问题。

问题分析

传统WinForm开发中,界面(UI)、业务逻辑与数据访问常混杂在同一代码文件中,导致三大问题:

1、代码耦合度高:修改需求需同时调整多个层面,易引入新Bug。

2、难以测试:UI控件与业务逻辑绑定,单元测试需模拟控件行为,成本高。

3、维护成本高:需求变更时,需重新梳理代码逻辑,效率低下。

MVP模式通过将应用拆分为Model(模型)、View(视图)、Presenter(展示器)三部分,有效解决上述问题。

项目效果


解决方案一

清晰的MVP架构

核心思想:

  • Model:封装数据与业务逻辑,独立于UI。

  • View:仅负责界面展示,通过接口与Presenter通信。

  • Presenter:协调Model与View,处理用户交互。

架构组件关系图:

实战代码

1、设备模型设计

public classEquipment : INotifyPropertyChanged
{
    privatestring _id;
    privatestring _name;
    private EquipmentStatus _status;
    privatedouble _temperature;
    privatedouble _pressure;
    privatedouble _speed;
    private DateTime _lastUpdated;

    publicstring Id
    {
        get => _id;
        set
        {
            _id = value;
            OnPropertyChanged(nameof(Id));
        }
    }

    // 温度属性 - 支持数据绑定
    publicdouble Temperature
    {
        get => _temperature;
        set
        {
            _temperature = value;
            OnPropertyChanged(nameof(Temperature));
        }
    }

    publicevent PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(thisnew PropertyChangedEventArgs(propertyName));
    }
}

publicenum EquipmentStatus
{
    Stopped,    // 停止
    Running,    // 运行中
    Warning,    // 警告
    Error,      // 故障
    Maintenance // 维护中
}

关键亮点

  • 实现INotifyPropertyChanged接口,支持数据绑定。

  • 枚举定义设备状态(如RunningError),提升可读性。

2、视图接口设计

public interfaceIMainView
{
    // 属性绑定
    string SelectedEquipmentId { get; }
    Equipment CurrentEquipment { getset; }
    List<Equipment> EquipmentList { set; }

    // 用户交互事件
    event EventHandler ViewLoaded;
    event EventHandler<string> EquipmentSelected;
    event EventHandler StartEquipmentClicked;
    event EventHandler StopEquipmentClicked;

    // 界面操作方法
    void ShowMessage(string message, string title = "信息");
    void ShowError(string message, string title = "错误");
    void UpdateEquipmentStatus(Equipment equipment);
}

设计亮点

  • 接口分离原则,便于Mock测试。

  • 事件驱动模式,解耦用户操作与业务逻辑。

解决方案二:实现高效的Presenter层

Presenter层作为Model与View的桥梁,需处理异步操作与异常。

例如,设备启动逻辑:

namespaceAppMVPIndustrialMonitorSystem.Presenters
{
    publicclassMainPresenter
    {
        privatereadonly IMainView _view;
        privatereadonly IEquipmentService _equipmentService;

        public MainPresenter(IMainView view, IEquipmentService equipmentService)
        {
            _view = view ?? thrownew ArgumentNullException(nameof(view));
            _equipmentService = equipmentService ?? thrownew ArgumentNullException(nameof(equipmentService));

            SubscribeToEvents();
        }

        private void SubscribeToEvents()
        {
            _view.ViewLoaded += OnViewLoaded;
            _view.EquipmentSelected += OnEquipmentSelected;
            _view.StartEquipmentClicked += OnStartEquipmentClicked;
            _view.StopEquipmentClicked += OnStopEquipmentClicked;
            _view.RefreshDataClicked += OnRefreshDataClicked;
            _view.UpdateParametersClicked += OnUpdateParametersClicked;

            _equipmentService.EquipmentStatusChanged += OnEquipmentStatusChanged;
        }

        private async void OnViewLoaded(object sender, EventArgs e)
        {
            try
            {
                await LoadEquipmentListAsync();
            }
            catch (Exception ex)
            {
                _view.ShowError($"加载设备列表失败:{ex.Message}");
            }
        }

        private async void OnEquipmentSelected(object sender, string equipmentId)
        {
            try
            {
                if (string.IsNullOrEmpty(equipmentId))
                {
                    _view.CurrentEquipment = null;
                    return;
                }

                var equipment = await _equipmentService.GetEquipmentByIdAsync(equipmentId);
                _view.CurrentEquipment = equipment;

                // 加载生产数据
                await LoadProductionDataAsync(equipmentId);
            }
            catch (Exception ex)
            {
                _view.ShowError($"加载设备详情失败:{ex.Message}");
            }
        }

        private async void OnStartEquipmentClicked(object sender, EventArgs e)
        {
            try
            {
                var equipmentId = _view.SelectedEquipmentId;
                if (string.IsNullOrEmpty(equipmentId))
                {
                    _view.ShowMessage("请先选择设备");
                    return;
                }

                var success = await _equipmentService.StartEquipmentAsync(equipmentId);
                if (success)
                {
                    _view.ShowMessage("设备启动成功");
                }
                else
                {
                    _view.ShowError("设备启动失败");
                }
            }
            catch (Exception ex)
            {
                _view.ShowError($"启动设备失败:{ex.Message}");
            }
        }

        private async void OnStopEquipmentClicked(object sender, EventArgs e)
        {
            try
            {
                var equipmentId = _view.SelectedEquipmentId;
                if (string.IsNullOrEmpty(equipmentId))
                {
                    _view.ShowMessage("请先选择设备");
                    return;
                }

                var success = await _equipmentService.StopEquipmentAsync(equipmentId);
                if (success)
                {
                    _view.ShowMessage("设备停止成功");
                }
                else
                {
                    _view.ShowError("设备停止失败");
                }
            }
            catch (Exception ex)
            {
                _view.ShowError($"停止设备失败:{ex.Message}");
            }
        }

        private async void OnRefreshDataClicked(object sender, EventArgs e)
        {
            try
            {
                await LoadEquipmentListAsync();

                var equipmentId = _view.SelectedEquipmentId;
                if (!string.IsNullOrEmpty(equipmentId))
                {
                    await LoadProductionDataAsync(equipmentId);
                }

                _view.ShowMessage("数据刷新成功");
            }
            catch (Exception ex)
            {
                _view.ShowError($"刷新数据失败:{ex.Message}");
            }
        }

        private async void OnUpdateParametersClicked(object sender, EventArgs e)
        {
            try
            {
                var currentEquipment = _view.CurrentEquipment;
                if (currentEquipment == null)
                {
                    _view.ShowMessage("请先选择设备");
                    return;
                }

                var success = await _equipmentService.UpdateEquipmentAsync(currentEquipment);
                if (success)
                {
                    _view.ShowMessage("参数更新成功");
                }
                else
                {
                    _view.ShowError("参数更新失败");
                }
            }
            catch (Exception ex)
            {
                _view.ShowError($"更新参数失败:{ex.Message}");
            }
        }

        private void OnEquipmentStatusChanged(object sender, Equipment equipment)
        {
            _view.UpdateEquipmentStatus(equipment);
        }

        private async Task LoadEquipmentListAsync()
        {
            var equipments = await _equipmentService.GetAllEquipmentAsync();
            _view.EquipmentList = equipments;
        }

        private async Task LoadProductionDataAsync(string equipmentId)
        {
            var endTime = DateTime.Now;
            var startTime = endTime.AddHours(-1); // 获取最近1小时的数据

            var productionData = await _equipmentService.GetProductionDataAsync(equipmentId, startTime, endTime);
            _view.ProductionDataList = productionData;
        }
    }
}

核心优势

  • 依赖注入(如IEquipmentService)提升可测试性。

  • 统一异常处理机制,避免UI阻塞。

解决方案三:服务层的实时数据处理

通过Timer模拟传感器数据更新,并通过事件通知UI:

public classEquipmentService : IEquipmentService
{
    privatereadonly List<Equipment> _equipments;
    privatereadonly Timer _statusUpdateTimer;
    privatereadonly Random _random;

    publicevent EventHandler<Equipment> EquipmentStatusChanged;

    public EquipmentService()
    {
        _random = new Random();
        _equipments = GenerateTestEquipment();
        
        // 🔥 关键:使用定时器模拟实时数据更新
        _statusUpdateTimer = new Timer(UpdateEquipmentStatus, null
            TimeSpan.Zero, TimeSpan.FromSeconds(5));
    }

    private void UpdateEquipmentStatus(object state)
    {
        foreach (var equipment in _equipments.Where(e => e.Status == EquipmentStatus.Running))
        {
            // 模拟传感器数据变化
            var tempChange = (_random.NextDouble() - 0.5) * 2;
            equipment.Temperature = Math.Max(0, Math.Min(200, equipment.Temperature + tempChange));

            var pressureChange = (_random.NextDouble() - 0.5) * 0.5;
            equipment.Pressure = Math.Max(0, Math.Min(20, equipment.Pressure + pressureChange));

            equipment.LastUpdated = DateTime.Now;

            // 🚀 触发状态变更事件
            EquipmentStatusChanged?.Invoke(this, equipment);
        }
    }

    public async Task<boolStartEquipmentAsync(string id)
    {
        await Task.Delay(100); // 模拟网络延迟
        
        var equipment = _equipments.FirstOrDefault(e => e.Id == id);
        if (equipment != null)
        {
            equipment.Status = EquipmentStatus.Running;
            equipment.LastUpdated = DateTime.Now;
            EquipmentStatusChanged?.Invoke(this, equipment);
            returntrue;
        }
        returnfalse;
    }
}

实战技巧

  • 边界值控制(如温度范围0-200℃)避免异常数据。

  • 事件机制实现解耦,避免直接引用View。

解决方案四:UI层的优雅实现

跨线程更新UI时,需通过Invoke确保线程安全:

public partialclassFrmMain : Form, IMainView
{
    public void UpdateEquipmentStatus(Equipment equipment)
    {
        // 关键:处理跨线程调用
        if (InvokeRequired)
        {
            Invoke(new Action<Equipment>(UpdateEquipmentStatus), equipment);
            return;
        }

        // 更新列表中的设备状态
        for (int i = 0; i < lstEquipment.Items.Count; i++)
        {
            if (lstEquipment.Items[i] is EquipmentListItem item && 
                item.Equipment.Id == equipment.Id)
            {
                item.Equipment = equipment;
                // 刷新显示
                var tempItem = lstEquipment.Items[i];
                lstEquipment.Items.RemoveAt(i);
                lstEquipment.Items.Insert(i, tempItem);
                break;
            }
        }

        // 更新状态栏
        tslStatus.Text = $"设备 {equipment.Name} 状态已更新";
    }

    private void UpdateStatusDisplay(EquipmentStatus status)
    {
        switch (status)
        {
            case EquipmentStatus.Running:
                lblStatusValue.Text = "运行中";
                lblStatusValue.ForeColor = Color.Green;
                break;
            case EquipmentStatus.Warning:
                lblStatusValue.Text = "警告";
                lblStatusValue.ForeColor = Color.Orange;
                break;
            case EquipmentStatus.Error:
                lblStatusValue.Text = "故障";
                lblStatusValue.ForeColor = Color.Red;
                break;
        }
    }
}

解决方案五:异常处理最佳实践

统一捕获异常并分类处理:

private async void OnRefreshDataClicked(object sender, EventArgs e)
{
    try
    {
        //使用using确保资源释放
        using (var loadingForm = new LoadingForm())
        {
            loadingForm.Show();
            
            await LoadEquipmentListAsync();
            
            var equipmentId = _view.SelectedEquipmentId;
            if (!string.IsNullOrEmpty(equipmentId))
            {
                await LoadProductionDataAsync(equipmentId);
            }
        }
        
        _view.ShowMessage("数据刷新成功");
    }
    catch (HttpRequestException httpEx)
    {
        _view.ShowError($"网络连接失败:{httpEx.Message}");
    }
    catch (TimeoutException timeoutEx)
    {
        _view.ShowError($"操作超时:{timeoutEx.Message}");
    }
    catch (Exception ex)
    {
        _view.ShowError($"刷新数据失败:{ex.Message}");
        // 记录详细日志
        Logger.LogError(ex, "RefreshData failed");
    }
}

核心要点

1、架构设计:MVP模式通过职责分离降低耦合度,提升可维护性。

2、实时处理:Timer+事件机制实现高效数据更新,避免直接轮询。

3、异步编程:async/await简化异步操作,统一异常处理提升用户体验。

4、线程安全:跨线程更新UI时使用Invoke,防止界面卡死。

总结

MVP架构为工业监控系统提供清晰的代码结构与高效的维护方案。

  • 如何设计解耦的MVP组件。

  • 如何实现实时数据更新与异步操作。

  • 如何统一处理异常与日志。

关键词

#MVP架构、C#、#工业监控#实时数据#异步编程#异常处理#WinForm#解耦设计#数据绑定#异步处理#实时更新

最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。也可以加入微信公众号[DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

作者:技术老小子

出处:weixin.qq.com/s/0TB5PB1r4uDhMWwm_cp5jw
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!



END



方便大家交流、资源共享和共同成长
纯技术交流群,需要加入的小伙伴请扫码,并备注加群



推荐阅读






.NET 9 可视化工作流引擎!纯血开源,支持数十种数据库,开箱即用

WinForm + STM32 打造稳定好用的工业设备远程升级工具

Visual Studio 2026 上手体验,AI 懂你、界面清爽、协作无缝

C# 打造轻量级上位机,高效打通 MES 与视觉检测系统

不用 GPU 也能跑的 WPF 视觉检测软件(Emgu CV + SQLite)

C# 工业级扫码难题破解,用微信实现精准扫码并自动填入任意应用

工业软件缺好 UI?这套 .NET 控件库从 IO 灯到圆角按钮全搞定

WPF 如何支撑一个灵活的流程图编辑器?

基于 WinForm GDI+ 的高性能矢量画布组件

WPF + MVVM 重塑康耐视 VisionPro 调试工具
WinForm + MVP 架构联合:打造超实用图书管理系统
WPF 高颜值工业上位机快速开发框架 
WinForm +SQLite 开发的高效PLC数据采集系统
C# 开发的串口固件传输工具 支持OTA升级
C# 实现海康相机 + PLC + 数据库的工业通信集成
WinForm 无线环境监控上位机系统设计与实现
工业自动化实战:C# 实现 Basler 相机图像采集系统
C# 基于机器视觉的液体颜色识别系统

C# 实现 GB28181标准与流媒体推流的完整指南

基于 .NET + Vue 3 的线路图绘制系统实战(含源码)

WinForm 下基于策略与工厂模式的 PLC 数据采集与监控系统

C# 开发工业级温湿度上位机:实时采集与存储

面向工业自动化的 WPF PLC 风格上位机开发框架

C# 写的一个开源免费的OPC UA网关,支持西门子PLC

WinForm + FFmpeg 开发的轻量级视频压缩工具

.NET 8 + Avalonia 跨平台简易校园信息管理系统的开发实战

Windows 服务可视化管理器:安装、启停、定时全搞定

C# + WPF + SuperSocket 开发面向工业自动化的 MES 系统

告别服务宕机,C# 看门狗守护你的 WinForm 与 Windows 服务

.NET 一款高效跨平台的自动更新工具(差异更新+热修复+自动升级)

面向工厂自动化的智能语音播报方案(基于.NET Windows服务)

基于 WPF + Prism 的工业自动化监控系统开源实践

工业自动化UI太难做?WPF 这套工业级控件方案真香(附源码)

工业自动化 WPF + Halcon 的模块化机器视觉解决方案

C# 开源视觉与运动控制集成平台,模块化设计赋能工业自动化

开源福利!八款 WPF + HandyControl 工业管理系统源码全公开

WinForm + Win32 API 自定义无边框窗口实战(工业软件必备)

WPF + MVVM架构的轻量级视频播放器实现

基于 HslCommunication 的多端同步PLC远程监控系统

C# + Vue 面向工业场景的实时数据采集与监控平台

WinForm 数据采集实战:从串口通信到MES对接的轻量化解决方案

一个拒绝过度设计的 .NET 快速开发框架:开箱即用,专注"干活"

C# 工业视觉全流程实战:模板匹配、胶钉定位与下位机通信
WPF 通信控制台:功能丰富、界面美观的上位机开发实战
拿来就用!一个基于 .NET 6 + WPF 的开源数据大屏模板

WinForm + SunnyUI  与 MQTTnet 实现智能可视化的火警联动大屏系统

.NET 9 + WPF + Halcon 构建工业视觉流程框架:从架构设计到落地实践

WinForm 高分屏适配难题?一款强大的控件自适应缩放工具

基于 .NET 6 + OpenCVSharp 的跨平台工业视觉图像分析工具
WinForm 框架下的工控领域视觉检测
基于 .NET 8 + React 的轻量高效库存管理系统(前后端分离)
WPF 实时工业监控大屏:ModBus协议集成与无边框动态可视化方案
图形化操作 Windows 服务?这个开源小工具做到了
.NET 9.0 一个可复用 WPF 界面框架
手把手教会设计 WinForm 高DPI兼容程序,告别字体模糊与控件乱飞

WPF + MVVM 自助式检验报告打印机的多框架实现

为什么 .NET 内存占用非常大?

C# 部署 Yolov8 全攻略:OpenVINO 与 TensorRT  双引擎加速
WPF 一款通用的嵌入式测控上位机(灵活配置免重复)
全栈 .NET 低代码引擎:权限、工作流、API动态生成,开源即用
一款基于 .NET 的轻量级 ERP 进销存系统:扫码入库、订单变标签,直达发货
.NET 8 + Vue 3 的智能工厂 MES 快速开发框架:设备监控、数据大屏全覆盖
.NET 9 + React 基于 DDD架构的动态路由 + RBAC权限实战
基于 SunnyUI 的企业级 WinForm 快速开发框架,开箱即用!
免硬件方案!基于.NET 的摄像头扫码工具(支持回车/连续扫描)
工业级 MES 系统开发 WPF + MVVM 从入门到实战(全源码/收藏版)


觉得有收获?不妨分享让更多人受益

关注「DotNet技术匠」,共同提升技术实力


收藏
点赞
分享
在看

【声明】内容源于网络
0
0
DotNet技术匠
「DotNet技术匠」聚焦.NET核心,分享深度干货、实战技巧、最新资讯、优质资源,助你领跑技术赛道,赋能开发者成长。
内容 1715
粉丝 0
DotNet技术匠 「DotNet技术匠」聚焦.NET核心,分享深度干货、实战技巧、最新资讯、优质资源,助你领跑技术赛道,赋能开发者成长。
总阅读32
粉丝0
内容1.7k