大数跨境
0
0

C# 高性能工业级数据采集系统

C# 高性能工业级数据采集系统 DotNet技术匠
2025-11-18
0
导读:工业4.0下的 C# 实战:三层异步架构实现智能数据采集与质量控制。

前言

工业4.0与数字化转型浪潮下,实时、可靠的数据采集能力已成为智能制造、物联网及金融监控等关键领域的基础设施。然而,许多 C# 开发在开发此类系统时,常面临高延迟、内存膨胀、异常处理缺失、质量控制薄弱等核心挑战。本文通过一个完整、可落地的实战架构,展示如何利用现代 .NET 技术栈打造专业级数据采集系统,兼顾性能、稳定性与扩展性。

项目效果

一、系统架构设计:三层异步流水线

我们摒弃传统的单线程轮询模式,采用 生产者-消费者模型 + Channel 异步通道,构建如下四层数据流:

数据生成层 → 数据处理层 → 数据聚合层 → UI 展示层

该架构具备三大优势:

高内聚低耦合:各模块职责清晰,便于独立测试与迭代

高吞吐低延迟:基于 System.Threading.Channels 的无锁异步通信

强容错能力:任一层异常不会导致全局崩溃,支持优雅降级

二、核心技术实现

1、智能数据生成器(模拟真实传感器)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespaceAppDataAcquisitionSystem
{
publicclassDataGenerator
    {
privatereadonly Random _random;
privatedouble _baseValue;
privatedouble _trend;

publicDataGenerator()
        {
            _random = new Random();
            _baseValue = 50.0;
            _trend = 0.0;
        }

publicasync Task<DataPoint> GenerateDataPointAsync()
        {
// 模拟真实的传感器数据:基础值 + 趋势 + 噪声 + 偶尔的异常值
var noise = _random.NextDouble() * 2 - 1// -1 到 1 的噪声
var spike = _random.NextDouble() < 0.05 ? _random.NextDouble() * 20 - 10 : 0// 5%概率出现异常值

            _trend += (_random.NextDouble() - 0.5) * 0.1
            _trend = Math.Max(-2, Math.Min(2, _trend)); 

            _baseValue += _trend;
            _baseValue = Math.Max(0, Math.Min(100, _baseValue)); // 限制基础值范围

varvalue = _baseValue + noise + spike;

returnnew DataPoint
            {
                Timestamp = DateTime.Now,
                Value = value,
                Quality = DataQuality.Good,
                Source = "Sensor-001"
            };
        }
    }
}

设计亮点:引入趋势项 _trend 模拟缓慢漂移,5% 异常率贴近工业现场,数值边界约束防止溢出。

2、高性能数据处理器(Channel + 异步流)

publicclass DataProcessor
{
privatereadonly Channel<DataPoint> _rawDataChannel;
privatereadonly Channel<DataPoint> _processedDataChannel;
privatereadonly Channel<AggregatedData> _aggregatedDataChannel;

publicasync Task StartProcessingAsync(CancellationToken cancellationToken = default)
    {
// 🚀 并行处理:数据处理和聚合同时进行
var processingTask = ProcessDataAsync(cancellationToken);
var aggregationTask = AggregateDataAsync(cancellationToken);

await Task.WhenAll(processingTask, aggregationTask);
    }

privateasync Task ProcessDataAsync(CancellationToken cancellationToken)
    {
awaitforeach (var dataPoint in _rawDataChannel.Reader.ReadAllAsync(cancellationToken))
        {
var processedPoint = ProcessSingleDataPoint(dataPoint);
if (processedPoint != null)
            {
await _processedDataChannel.Writer.WriteAsync(processedPoint, cancellationToken);
            }
        }
    }
}

关键点:Channel<T> 提供背压控制;IAsyncEnumerable 支持响应式流处理;并行任务提升吞吐量。

3、多维度质量控制机制

private DataQuality PerformQualityCheck(DataPoint dataPoint)
{
// 🛡️ 基础数值检查
if (double.IsNaN(dataPoint.Value) || double.IsInfinity(dataPoint.Value))
return DataQuality.Bad;

// 📊 数值范围验证
if (Math.Abs(dataPoint.Value) > 1000)
return DataQuality.Bad;

// ⚡ 变化率检查 - 防止突变数据
if (_lastValue != null)
    {
var timeDiff = (dataPoint.Timestamp - _lastValue.Timestamp).TotalSeconds;
if (timeDiff > 0)
        {
var changeRate = Math.Abs(dataPoint.Value - _lastValue.Value) / timeDiff;
if (changeRate > 100// 变化率阈值
return DataQuality.Uncertain;
        }
    }

return DataQuality.Good;
}

该策略有效识别 NaN、超限值及不合理跳变,保障下游数据可信度。

4、滑动窗口实时聚合

privateasync Task AggregateDataAsync(CancellationToken cancellationToken)
{
awaitforeach (var dataPoint in _processedDataChannel.Reader.ReadAllAsync(cancellationToken))
    {
        _windowBuffer.Add(dataPoint);

if (_windowBuffer.Count >= _config.WindowSize)
        {
var aggregated = CalculateAggregatedData(_windowBuffer);
await _aggregatedDataChannel.Writer.WriteAsync(aggregated, cancellationToken);

// 🎯 滑动窗口优化:保留部分历史数据
var keepCount = _config.WindowSize / 2;
            _windowBuffer.RemoveRange(0, _windowBuffer.Count - keepCount);
        }
    }
}

滑动窗口避免“硬切分”导致的信息丢失,同时控制内存占用,实现连续性与效率的平衡。

三、UI 实时可视化(ScottPlot 集成)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using ScottPlot;
using Timer = System.Threading.Timer;

namespaceAppDataAcquisitionSystem
{
publicpartialclassForm1 : Form
    {
private DataGenerator _dataGenerator;
private DataProcessor _dataProcessor;
private AcquisitionConfig _config;
private Timer _acquisitionTimer;
private CancellationTokenSource _cancellationTokenSource;

// 数据存储
private List<DataPoint> _realTimeData;
private List<AggregatedData> _aggregatedData;

// 图表数据
private List<double> _realTimeValues;
private List<DateTime> _realTimeTimes;
private List<double> _aggregatedValues;
private List<DateTime> _aggregatedTimes;

privateconstint MaxDisplayPoints = 100;

publicForm1()
        {
            InitializeComponent();
            InitializeSystem();
            InitializePlots();
            AttachEventHandlers();
        }

privatevoidInitializeSystem()
        {
            _realTimeData = new List<DataPoint>();
            _aggregatedData = new List<AggregatedData>();
            _realTimeValues = new List<double>();
            _realTimeTimes = new List<DateTime>();
            _aggregatedValues = new List<double>();
            _aggregatedTimes = new List<DateTime>();

            _dataGenerator = new DataGenerator();
            UpdateConfiguration();
        }

privatevoidInitializePlots()
        {
            plotRealTime.Font = new Font("SimSun"12);
// 配置实时数据图表   
            plotRealTime.Plot.Title("实时数据");
            plotRealTime.Plot.Font.Set("SimSun");
            plotRealTime.Plot.XLabel("时间");
            plotRealTime.Plot.YLabel("数值");

// 配置聚合数据图表   
            plotAggregated.Font = new Font("SimSun"12);
            plotAggregated.Plot.Title("聚合数据");
            plotAggregated.Plot.Font.Set("SimSun");
            plotAggregated.Plot.XLabel("时间");
            plotAggregated.Plot.YLabel("聚合值");
        }

privatevoidAttachEventHandlers()
        {
            btnStart.Click += BtnStart_Click;
            btnStop.Click += BtnStop_Click;
            btnClear.Click += BtnClear_Click;

// 配置变更事件
            nudSampleRate.ValueChanged += (s, e) => UpdateConfiguration();
            nudNoiseThreshold.ValueChanged += (s, e) => UpdateConfiguration();
            nudWindowSize.ValueChanged += (s, e) => UpdateConfiguration();
            chkQualityCheck.CheckedChanged += (s, e) => UpdateConfiguration();
            chkDenoising.CheckedChanged += (s, e) => UpdateConfiguration();
            cmbAggregationType.SelectedIndexChanged += (s, e) => UpdateConfiguration();
        }

privatevoidUpdateConfiguration()
        {
            _config = new AcquisitionConfig
            {
                SampleRateMs = (int)nudSampleRate.Value,
                NoiseThreshold = (double)nudNoiseThreshold.Value,
                WindowSize = (int)nudWindowSize.Value,
                EnableQualityCheck = chkQualityCheck.Checked,
                EnableDenoising = chkDenoising.Checked,
                AggregationType = (AggregationType)cmbAggregationType.SelectedIndex
            };
        }

private async voidBtnStart_Click(object sender, EventArgs e)
        {
try
            {
                btnStart.Enabled = false;
                btnStop.Enabled = true;

                UpdateConfiguration();
                _dataProcessor = new DataProcessor(_config);
                _cancellationTokenSource = new CancellationTokenSource();

// 启动数据处理
var processingTask = _dataProcessor.StartProcessingAsync(_cancellationTokenSource.Token);

// 启动数据读取任务
var readingTask = StartDataReadingAsync(_cancellationTokenSource.Token);

// 启动数据采集定时器
                _acquisitionTimer = new Timer(OnDataAcquisitionTimer, null0, _config.SampleRateMs);

                UpdateStatus("系统已启动");

await Task.WhenAny(processingTask, readingTask);
            }
catch (Exception ex)
            {
                UpdateStatus($"启动失败: {ex.Message}");
                btnStart.Enabled = true;
                btnStop.Enabled = false;
            }
        }

privatevoidBtnStop_Click(object sender, EventArgs e)
        {
            StopAcquisition();
        }

privatevoidBtnClear_Click(object sender, EventArgs e)
        {
            _realTimeData.Clear();
            _aggregatedData.Clear();
            _realTimeValues.Clear();
            _realTimeTimes.Clear();
            _aggregatedValues.Clear();
            _aggregatedTimes.Clear();

            plotRealTime.Plot.Clear();
            plotAggregated.Plot.Clear();
            plotRealTime.Refresh();
            plotAggregated.Refresh();

            UpdateStatus("数据已清除");
        }

privatevoidStopAcquisition()
        {
try
            {
                _acquisitionTimer?.Dispose();
                _cancellationTokenSource?.Cancel();
                _dataProcessor?.StopProcessing();

                btnStart.Enabled = true;
                btnStop.Enabled = false;

                UpdateStatus("系统已停止");
            }
catch (Exception ex)
            {
                UpdateStatus($"停止失败: {ex.Message}");
            }
        }

privateasyncvoidOnDataAcquisitionTimer(object state)
        {
try
            {
var dataPoint = await _dataGenerator.GenerateDataPointAsync();
await _dataProcessor.AddRawDataAsync(dataPoint);
            }
catch (Exception ex)
            {
                BeginInvoke(new Action(() => UpdateStatus($"数据采集错误: {ex.Message}")));
            }
        }

privateasync Task StartDataReadingAsync(CancellationToken cancellationToken)
        {
// 启动处理后数据读取
var processedTask = ReadProcessedDataAsync(cancellationToken);
var aggregatedTask = ReadAggregatedDataAsync(cancellationToken);

await Task.WhenAll(processedTask, aggregatedTask);
        }

privateasync Task ReadProcessedDataAsync(CancellationToken cancellationToken)
        {
try
            {
awaitforeach (var dataPoint in _dataProcessor.ProcessedDataReader.ReadAllAsync(cancellationToken))
                {
                    BeginInvoke(new Action(() =>
                    {
                        _realTimeData.Add(dataPoint);
                        _realTimeValues.Add(dataPoint.Value);
                        _realTimeTimes.Add(dataPoint.Timestamp);

// 限制显示点数
if (_realTimeValues.Count > MaxDisplayPoints)
                        {
                            _realTimeValues.RemoveAt(0);
                            _realTimeTimes.RemoveAt(0);
                        }

                        UpdateRealTimePlot();
                        UpdateStatus($"实时数据: {dataPoint.Value:F2} (质量: {dataPoint.Quality})");
                    }));
                }
            }
catch (OperationCanceledException)
            {
// 正常取消
            }
        }

privateasync Task ReadAggregatedDataAsync(CancellationToken cancellationToken)
        {
try
            {
awaitforeach (var aggregatedData in _dataProcessor.AggregatedDataReader.ReadAllAsync(cancellationToken))
                {
                    BeginInvoke(new Action(() =>
                    {
                        _aggregatedData.Add(aggregatedData);
                        _aggregatedValues.Add(aggregatedData.Value);
                        _aggregatedTimes.Add(aggregatedData.WindowEnd);

// 限制显示点数
if (_aggregatedValues.Count > MaxDisplayPoints)
                        {
                            _aggregatedValues.RemoveAt(0);
                            _aggregatedTimes.RemoveAt(0);
                        }

                        UpdateAggregatedPlot();
                        UpdateStatus($"聚合数据 ({aggregatedData.Type}): {aggregatedData.Value:F2} (样本数: {aggregatedData.SampleCount})");
                    }));
                }
            }
catch (OperationCanceledException)
            {
// 正常取消
            }
        }

privatevoidUpdateRealTimePlot()
        {
if (_realTimeValues.Count == 0return;

            plotRealTime.Plot.Clear();

var times = _realTimeTimes.Select(t => t.ToOADate()).ToArray();
var values = _realTimeValues.ToArray();

var scatter = plotRealTime.Plot.Add.Scatter(times, values);
            scatter.Color = Colors.Blue;
            scatter.LineWidth = 2;

            plotRealTime.Plot.Axes.DateTimeTicksBottom();
            plotRealTime.Plot.Axes.AutoScale();
            plotRealTime.Refresh();
        }

privatevoidUpdateAggregatedPlot()
        {
if (_aggregatedValues.Count == 0return;

            plotAggregated.Plot.Clear();

var times = _aggregatedTimes.Select(t => t.ToOADate()).ToArray();
var values = _aggregatedValues.ToArray();

var scatter = plotAggregated.Plot.Add.Scatter(times, values);
            scatter.Color = Colors.Red;
            scatter.LineWidth = 3;

            plotAggregated.Plot.Axes.DateTimeTicksBottom();
            plotAggregated.Plot.Axes.AutoScale();
            plotAggregated.Refresh();
        }

privatevoidUpdateStatus(string message)
        {
if (InvokeRequired)
            {
                BeginInvoke(new Action(() => UpdateStatus(message)));
return;
            }

            txtStatus.AppendText($"[{DateTime.Now:HH:mm:ss}{message}rn");
            txtStatus.ScrollToCaret();
        }

protectedoverridevoidOnFormClosing(FormClosingEventArgs e)
        {
            StopAcquisition();
base.OnFormClosing(e);
        }
    }
}
四、性能优化要点

内存管理

privateconstint MaxDisplayPoints = 100;

// 限制显示点数,防止内存泄漏
if (_realTimeValues.Count > MaxDisplayPoints)
{
    _realTimeValues.RemoveAt(0);
    _realTimeTimes.RemoveAt(0);
}

异步资源清理

// 正确的取消令牌使用
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

// 优雅的资源清理
protectedoverridevoidOnFormClosing(FormClosingEventArgs e)
{
    StopAcquisition(); // 确保所有异步任务正确停止
base.OnFormClosing(e);
}

五、典型应用场景

工业物联网(IIoT):产线传感器实时监控

金融交易系统:毫秒级行情数据采集

环境监测站:温湿度、PM2.5 连续记录

IT 运维平台:服务器 CPU/内存指标追踪

六、常见问题应对

Q1:Channel 背压如何处理?

A:使用有界通道并配置等待策略

var options = new BoundedChannelOptions(1000)
{
    FullMode = BoundedChannelFullMode.Wait,
    SingleReader = true,
    SingleWriter = false
};
_channel = Channel.CreateBounded<DataPoint>(options);

Q2:海量数据下的性能瓶颈?

A:引入批处理机制:

privatereadonly List<DataPoint> _batchBuffer = new List<DataPoint>();
privateconstint BatchSize = 50;

if (_batchBuffer.Count >= BatchSize)
{
await ProcessBatchAsync(_batchBuffer);
    _batchBuffer.Clear();
}

总结

一套面向工业场景的 C# 数据采集系统,其核心价值在于:

1、异步流水线架构:通过 Channel 实现高并发、低耦合的数据流转

2、全链路质量控制:从生成、处理到聚合,嵌入多层校验机制

3、轻量级实时可视化:ScottPlot 与 WinForm 无缝集成,支持动态刷新

该方案不仅解决了传统采集系统的性能与稳定性痛点,更为后续扩展(如接入数据库、远程上报、AI 异常检测)预留了清晰接口。开发者可根据实际需求灵活裁剪或增强各模块功能。

关键词

C#、#数据采集#Channel#异步编程#ScottPlot#工业物联网#质量控制#滑动窗口#WinForm、.NET

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

作者:技术老小子

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



END



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



推荐阅读






.NET 轻量级键鼠录制与回放自动化扩展库
开源 .NET 工作流引擎 + 可视化设计,轻松搞定 OA/CRM/ERP 开发
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核心,分享深度干货、实战技巧、最新资讯、优质资源,助你领跑技术赛道,赋能开发者成长。
总阅读22
粉丝0
内容1.7k