在制造业、智慧城市、农业物联网等领域每天都在上演。企业在物联网数据采集方面存在实时性、稳定性问题。如何构建一个高效、稳定的设备监控系统成为了每个.NET开发者必须面对的挑战。
今天,我将通过一个完整的C#物联网设备监控系统案例,手把手教你构建企业级的数据采集平台,解决实时监控、性能优化、数据可视化等核心问题。
🎯 项目背景与痛点分析
在实际的物联网项目中,我们经常遇到以下痛点:
🔥 核心痛点
数据采集不稳定
性能瓶颈
监控可视化困难
异常处理不完善
💼 业务需求
🛠️ 技术方案设计
🏗️ 系统架构

🔧 核心技术栈
并发处理
ConcurrentDictionary
确保线程安全定时任务
数据可视化
性能监控
异常处理
💻 核心代码实现
🎯 IoT设备核心类设计
// IoT设备基础枚举定义
publicenum DeviceType
{
TemperatureSensor, // 温度传感器
HumiditySensor, // 湿度传感器
PressureSensor, // 压力传感器
Motor, // 电机
Valve, // 阀门
Pump // 泵
}
publicenum DeviceStatus
{
Online, // 在线
Offline, // 离线
Error, // 故障
Maintenance // 维护中
}
// 设备核心类 - 每个设备都是一个独立的数据生产者
publicclass IoTDevice
{
publicstring DeviceId { get; set; }
publicstring DeviceName { get; set; }
public DeviceType DeviceType { get; set; }
public DeviceStatus Status { get; set; }
publicdouble? LastValue { get; set; }
public DateTime LastUpdateTime { get; set; }
private readonly Timer _dataTimer;
private readonly Random _random = new Random();
public IoTDevice(string deviceId, string deviceName, DeviceType deviceType)
{
DeviceId = deviceId;
DeviceName = deviceName;
DeviceType = deviceType;
Status = DeviceStatus.Offline;
LastUpdateTime = DateTime.Now;
// 🔥 关键设计:每个设备独立的数据生成定时器
_dataTimer = new Timer();
_dataTimer.Interval = 2000 + _random.Next(0, 3000); // 2-5秒随机间隔
_dataTimer.Tick += GenerateData;
}
private void GenerateData(object sender, EventArgs e)
{
// 🎯 根据设备类型生成不同范围的模拟数据
switch (DeviceType)
{
case DeviceType.TemperatureSensor:
LastValue = 20 + _random.NextDouble() * 30; // 20-50°C
break;
case DeviceType.HumiditySensor:
LastValue = 30 + _random.NextDouble() * 40; // 30-70%
break;
case DeviceType.PressureSensor:
LastValue = 100 + _random.NextDouble() * 200; // 100-300 kPa
break;
case DeviceType.Motor:
LastValue = 1000 + _random.NextDouble() * 2000; // 1000-3000 RPM
break;
default:
LastValue = _random.NextDouble() * 100;
break;
}
LastUpdateTime = DateTime.Now;
// 🚨 模拟设备故障:2%概率出错,10%概率自动恢复
if (_random.NextDouble() < 0.02)
{
Status = DeviceStatus.Error;
}
elseif (Status == DeviceStatus.Error && _random.NextDouble() < 0.1)
{
Status = DeviceStatus.Online;
}
}
public void Start()
{
Status = DeviceStatus.Online;
_dataTimer.Start();
}
public void Stop()
{
Status = DeviceStatus.Offline;
_dataTimer.Stop();
}
}
🎯 设备管理器 - 并发安全的核心
// 设备管理器 - 系统的核心控制器
publicclass IoTDeviceManager : IDisposable
{
// 🔥 使用ConcurrentDictionary确保线程安全
private readonly ConcurrentDictionary<string, IoTDevice> _devices;
private readonly Timer _statsTimer;
privatelong _totalDataPoints;
private DateTime _lastStatsTime;
public IoTDeviceManager()
{
_devices = new ConcurrentDictionary<string, IoTDevice>();
_lastStatsTime = DateTime.Now;
// 统计定时器 - 每秒更新性能数据
_statsTimer = new Timer();
_statsTimer.Interval = 1000;
_statsTimer.Tick += UpdateStats;
_statsTimer.Start();
}
public void AddDevice(IoTDevice device)
{
// 🎯 线程安全的设备添加
_devices.TryAdd(device.DeviceId, device);
}
public IoTDevice GetDevice(string deviceId)
{
_devices.TryGetValue(deviceId, out var device);
return device;
}
public IEnumerable<IoTDevice> GetAllDevices()
{
return _devices.Values;
}
// 🚀 批量操作 - 提高执行效率
public void StartAllDevices()
{
Parallel.ForEach(_devices.Values, device => device.Start());
}
public void StopAllDevices()
{
Parallel.ForEach(_devices.Values, device => device.Stop());
}
// 📊 实时计算数据采集速率
public double GetDataRate()
{
var now = DateTime.Now;
var elapsed = (now - _lastStatsTime).TotalSeconds;
var onlineDevices = _devices.Values.Count(d => d.Status == DeviceStatus.Online);
return elapsed > 0 ? onlineDevices / 2.5 : 0; // 平均每2.5秒一个数据点
}
private void UpdateStats(object sender, EventArgs e)
{
_totalDataPoints += _devices.Values.Count(d => d.Status == DeviceStatus.Online);
_lastStatsTime = DateTime.Now;
}
public void Dispose()
{
_statsTimer?.Dispose();
foreach (var device in _devices.Values)
{
device.Stop();
}
}
}
🎯 性能监控系统 - 企业级监控
// 性能监控收集器 - 实时追踪系统性能
publicclass MetricsCollector : IDisposable
{
private readonly ConcurrentDictionary<string, OperationMetrics> _operationMetrics;
private readonly ConcurrentDictionary<string, long> _dataVolumes;
private readonly ConcurrentQueue<ErrorRecord> _errors;
public MetricsCollector()
{
_operationMetrics = new ConcurrentDictionary<string, OperationMetrics>();
_dataVolumes = new ConcurrentDictionary<string, long>();
_errors = new ConcurrentQueue<ErrorRecord>();
}
// 🎯 记录操作性能指标
public void RecordProcessing(string operation, TimeSpan duration, bool success)
{
_operationMetrics.AddOrUpdate(operation,
new OperationMetrics
{
Count = 1,
TotalDuration = duration,
SuccessCount = success ? 1 : 0
},
(key, existing) => new OperationMetrics
{
Count = existing.Count + 1,
TotalDuration = existing.TotalDuration + duration,
SuccessCount = existing.SuccessCount + (success ? 1 : 0)
});
}
// 📊 生成性能报告
public PerformanceReport GenerateReport()
{
var totalProcessed = _operationMetrics.Values.Sum(m => m.Count);
var totalDuration = _operationMetrics.Values.Sum(m => m.TotalDuration.TotalMilliseconds);
var totalSuccess = _operationMetrics.Values.Sum(m => m.SuccessCount);
returnnew PerformanceReport
{
TimeWindow = TimeSpan.FromSeconds(10),
TotalProcessed = totalProcessed,
ProcessingRate = totalProcessed / 10.0,
AverageLatency = totalProcessed > 0 ? totalDuration / totalProcessed : 0,
ErrorRate = totalProcessed > 0 ? (double)(totalProcessed - totalSuccess) / totalProcessed : 0,
MemoryUsage = GC.GetTotalMemory(false) / 1024 / 1024
};
}
public void Dispose()
{
// 清理资源
}
}
// 性能报告数据结构
publicclass PerformanceReport
{
public TimeSpan TimeWindow { get; set; }
publiclong TotalProcessed { get; set; }
publicdouble ProcessingRate { get; set; } // 处理速率
publicdouble AverageLatency { get; set; } // 平均延迟
publicdouble ErrorRate { get; set; } // 错误率
publiclong MemoryUsage { get; set; } // 内存使用量
}
🎯 数据可视化实现
Nuget 安装ScottPlot.WinForms 5.0以上版本,这个版本变化较大。
using System.Collections.Concurrent;
using ScottPlot;
using System.Diagnostics;
using System.Text;
using Timer = System.Windows.Forms.Timer;
using ScottPlot.WinForms;
namespace AppIoTDeviceMonitor
{
public partial class Form1 : Form
{
private readonly IoTDeviceManager _deviceManager;
private readonly DataCollectionMonitor _monitor;
private readonly Timer _refreshTimer;
private readonly Random _random = new Random();
// ScottPlot 数据存储
private readonly ConcurrentQueue<(DateTime time, double temp)> _tempData = new();
private readonly ConcurrentQueue<(DateTime time, double humidity)> _humidityData = new();
private readonly ConcurrentQueue<(DateTime time, double pressure)> _pressureData = new();
// ScottPlot 绘图对象
private ScottPlot.Plottables.Scatter _tempPlot;
private ScottPlot.Plottables.Scatter _humidityPlot;
private ScottPlot.Plottables.Scatter _pressurePlot;
privateconstint MAX_DATA_POINTS = 100;
public Form1()
{
InitializeComponent();
_deviceManager = new IoTDeviceManager();
_monitor = new DataCollectionMonitor();
// 设置刷新定时器
_refreshTimer = new Timer();
_refreshTimer.Interval = 1000; // 1秒刷新一次
_refreshTimer.Tick += RefreshTimer_Tick;
InitializeDevices();
InitializeScottPlot();
InitializeDataGridView();
_refreshTimer.Start();
}
private void InitializeDevices()
{
// 添加示例设备
_deviceManager.AddDevice(new IoTDevice("TEMP_001", "温度传感器1", DeviceType.TemperatureSensor));
_deviceManager.AddDevice(new IoTDevice("TEMP_002", "温度传感器2", DeviceType.TemperatureSensor));
_deviceManager.AddDevice(new IoTDevice("HUM_001", "湿度传感器1", DeviceType.HumiditySensor));
_deviceManager.AddDevice(new IoTDevice("PRES_001", "压力传感器1", DeviceType.PressureSensor));
_deviceManager.AddDevice(new IoTDevice("MOTOR_001", "电机1", DeviceType.Motor));
// 启动所有设备
_deviceManager.StartAllDevices();
}
private void InitializeScottPlot()
{
// 设置图表基本属性
formsPlot.Plot.Title("IoT设备实时数据监控");
formsPlot.Plot.XLabel("时间");
formsPlot.Plot.YLabel("数值");
formsPlot.Plot.Font.Set("SimSun");
// 设置背景色
formsPlot.Plot.FigureBackground.Color = ScottPlot.Color.FromHex("#FAFAFA");
formsPlot.Plot.DataBackground.Color = ScottPlot.Color.FromHex("#FAFAFA");
// 配置坐标轴
formsPlot.Plot.Axes.DateTimeTicksBottom();
formsPlot.Plot.Axes.AutoScale();
// 创建数据系列
_tempPlot = formsPlot.Plot.Add.Scatter(newdouble[0], newdouble[0]);
_tempPlot.Color = ScottPlot.Color.FromHex("#FF0000");
_tempPlot.LineWidth = 2;
_tempPlot.MarkerSize = 3;
_tempPlot.LegendText = "温度 (°C)";
_humidityPlot = formsPlot.Plot.Add.Scatter(newdouble[0], newdouble[0]);
_humidityPlot.Color = ScottPlot.Color.FromHex("#0000FF"); ;
_humidityPlot.LineWidth = 2;
_humidityPlot.MarkerSize = 3;
_humidityPlot.LegendText = "湿度 (%)";
_pressurePlot = formsPlot.Plot.Add.Scatter(newdouble[0], newdouble[0]);
_pressurePlot.Color = ScottPlot.Color.FromHex("#008000"); ;
_pressurePlot.LineWidth = 2;
_pressurePlot.MarkerSize = 3;
_pressurePlot.LegendText = "压力 (kPa)";
// 显示图例
formsPlot.Plot.ShowLegend(Alignment.UpperLeft);
// 设置网格
formsPlot.Plot.Grid.MajorLineColor = ScottPlot.Color.FromHex("#C8C8C8");
formsPlot.Plot.Grid.MinorLineColor = ScottPlot.Color.FromHex("#E6E6E6");
// 初始刷新
formsPlot.Refresh();
}
private void InitializeDataGridView()
{
dgvDevices.AutoGenerateColumns = false;
dgvDevices.Columns.Add("DeviceId", "设备ID");
dgvDevices.Columns.Add("DeviceName", "设备名称");
dgvDevices.Columns.Add("DeviceType", "设备类型");
dgvDevices.Columns.Add("Status", "状态");
dgvDevices.Columns.Add("LastValue", "最新值");
dgvDevices.Columns.Add("LastUpdate", "最后更新");
// 设置列宽
dgvDevices.Columns[0].Width = 100;
dgvDevices.Columns[1].Width = 120;
dgvDevices.Columns[2].Width = 100;
dgvDevices.Columns[3].Width = 80;
dgvDevices.Columns[4].Width = 80;
dgvDevices.Columns[5].Width = 140;
}
private void RefreshTimer_Tick(object sender, EventArgs e)
{
UpdateDeviceStatus();
UpdateScottPlot();
UpdatePerformanceMetrics();
}
private void UpdateDeviceStatus()
{
dgvDevices.Rows.Clear();
foreach (var device in _deviceManager.GetAllDevices())
{
var row = new DataGridViewRow();
row.CreateCells(dgvDevices);
row.Cells[0].Value = device.DeviceId;
row.Cells[1].Value = device.DeviceName;
row.Cells[2].Value = device.DeviceType.ToString();
row.Cells[3].Value = device.Status.ToString();
row.Cells[4].Value = device.LastValue?.ToString("F2") ?? "-";
row.Cells[5].Value = device.LastUpdateTime.ToString("yyyy-MM-dd HH:mm:ss");
// 根据状态设置行颜色
if (device.Status == DeviceStatus.Online)
row.DefaultCellStyle.BackColor = System.Drawing.Color.LightGreen;
elseif (device.Status == DeviceStatus.Error)
row.DefaultCellStyle.BackColor = System.Drawing.Color.LightPink;
else
row.DefaultCellStyle.BackColor = System.Drawing.Color.LightGray;
dgvDevices.Rows.Add(row);
}
}
private void UpdateScottPlot()
{
var now = DateTime.Now;
// 获取并添加温度数据
var tempDevice = _deviceManager.GetDevice("TEMP_001");
if (tempDevice != null && tempDevice.LastValue.HasValue)
{
_tempData.Enqueue((now, tempDevice.LastValue.Value));
// 限制数据点数量
while (_tempData.Count > MAX_DATA_POINTS)
{
_tempData.TryDequeue(out _);
}
}
// 获取并添加湿度数据
var humDevice = _deviceManager.GetDevice("HUM_001");
if (humDevice != null && humDevice.LastValue.HasValue)
{
_humidityData.Enqueue((now, humDevice.LastValue.Value));
while (_humidityData.Count > MAX_DATA_POINTS)
{
_humidityData.TryDequeue(out _);
}
}
// 获取并添加压力数据
var presDevice = _deviceManager.GetDevice("PRES_001");
if (presDevice != null && presDevice.LastValue.HasValue)
{
_pressureData.Enqueue((now, presDevice.LastValue.Value));
while (_pressureData.Count > MAX_DATA_POINTS)
{
_pressureData.TryDequeue(out _);
}
}
// 更新图表数据
UpdatePlotData();
}
private void UpdatePlotData()
{
// 更新温度数据
if (_tempData.Count > 0)
{
var tempArray = _tempData.ToArray();
var tempX = tempArray.Select(d => d.time.ToOADate()).ToArray();
var tempY = tempArray.Select(d => d.temp).ToArray();
// 移除旧的图表并添加新的
formsPlot.Plot.Remove(_tempPlot);
_tempPlot = formsPlot.Plot.Add.Scatter(tempX, tempY);
_tempPlot.Color = ScottPlot.Color.FromHex("#FF0000");
_tempPlot.LineWidth = 2;
_tempPlot.MarkerSize = 3;
_tempPlot.LegendText = "温度 (°C)";
}
// 更新湿度数据
if (_humidityData.Count > 0)
{
var humArray = _humidityData.ToArray();
var humX = humArray.Select(d => d.time.ToOADate()).ToArray();
var humY = humArray.Select(d => d.humidity).ToArray();
formsPlot.Plot.Remove(_humidityPlot);
_humidityPlot = formsPlot.Plot.Add.Scatter(humX, humY);
_humidityPlot.Color = ScottPlot.Color.FromHex("#0000FF");
_humidityPlot.LineWidth = 2;
_humidityPlot.MarkerSize = 3;
_humidityPlot.LegendText = "湿度 (%)";
}
// 更新压力数据
if (_pressureData.Count > 0)
{
var presArray = _pressureData.ToArray();
var presX = presArray.Select(d => d.time.ToOADate()).ToArray();
var presY = presArray.Select(d => d.pressure).ToArray();
formsPlot.Plot.Remove(_pressurePlot);
_pressurePlot = formsPlot.Plot.Add.Scatter(presX, presY);
_pressurePlot.Color = ScottPlot.Color.FromHex("#008000");
_pressurePlot.LineWidth = 2;
_pressurePlot.MarkerSize = 3;
_pressurePlot.LegendText = "压力 (kPa)";
}
// 重新显示图例
formsPlot.Plot.ShowLegend(Alignment.UpperLeft);
// 自动缩放并刷新
formsPlot.Plot.Axes.AutoScale();
formsPlot.Refresh();
}
private void UpdatePerformanceMetrics()
{
// 更新性能指标显示
var totalDevices = _deviceManager.GetAllDevices().Count();
var onlineDevices = _deviceManager.GetAllDevices().Count(d => d.Status == DeviceStatus.Online);
var errorDevices = _deviceManager.GetAllDevices().Count(d => d.Status == DeviceStatus.Error);
lblTotalDevices.Text = $"总设备数: {totalDevices}";
lblOnlineDevices.Text = $"在线设备: {onlineDevices}";
lblErrorDevices.Text = $"故障设备: {errorDevices}";
// 更新系统性能
var process = Process.GetCurrentProcess();
lblMemoryUsage.Text = $"内存使用: {process.WorkingSet64 / 1024 / 1024:F1} MB";
lblCpuUsage.Text = $"CPU使用率: {GetCpuUsage():F1}%";
// 更新数据采集速率
lblDataRate.Text = $"数据采集速率: {_deviceManager.GetDataRate():F0} 条/秒";
}
private double GetCpuUsage()
{
// 简单的CPU使用率计算
return _random.NextDouble() * 20 + 10; // 模拟10-30%的CPU使用率
}
private void btnStartMonitoring_Click(object sender, EventArgs e)
{
_deviceManager.StartAllDevices();
_refreshTimer.Start();
btnStartMonitoring.Enabled = false;
btnStopMonitoring.Enabled = true;
MessageBox.Show("监控已启动", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void btnStopMonitoring_Click(object sender, EventArgs e)
{
_deviceManager.StopAllDevices();
_refreshTimer.Stop();
btnStartMonitoring.Enabled = true;
btnStopMonitoring.Enabled = false;
MessageBox.Show("监控已停止", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void btnResetChart_Click(object sender, EventArgs e)
{
// 清空所有数据
while (_tempData.TryDequeue(out _)) { }
while (_humidityData.TryDequeue(out _)) { }
while (_pressureData.TryDequeue(out _)) { }
// 完全清空图表并重新初始化
formsPlot.Plot.Clear();
InitializeScottPlot();
MessageBox.Show("图表已重置", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void btnExportData_Click(object sender, EventArgs e)
{
var saveDialog = new SaveFileDialog
{
Filter = "CSV files (*.csv)|*.csv",
Title = "导出设备数据"
};
if (saveDialog.ShowDialog() == DialogResult.OK)
{
ExportDeviceData(saveDialog.FileName);
MessageBox.Show("数据导出成功", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
private void ExportDeviceData(string fileName)
{
var csv = new StringBuilder();
csv.AppendLine("设备ID,设备名称,设备类型,状态,最新值,最后更新时间");
foreach (var device in _deviceManager.GetAllDevices())
{
csv.AppendLine($"{device.DeviceId},{device.DeviceName},{device.DeviceType}," +
$"{device.Status},{device.LastValue},{device.LastUpdateTime}");
}
System.IO.File.WriteAllText(fileName, csv.ToString(), Encoding.UTF8);
}
private void btnZoomIn_Click(object sender, EventArgs e)
{
formsPlot.Plot.Axes.Zoom(0.8, 0.8);
formsPlot.Refresh();
}
private void btnZoomOut_Click(object sender, EventArgs e)
{
formsPlot.Plot.Axes.Zoom(1.2, 1.2);
formsPlot.Refresh();
}
private void btnAutoScale_Click(object sender, EventArgs e)
{
formsPlot.Plot.Axes.AutoScale();
formsPlot.Refresh();
}
private void btnSaveChart_Click(object sender, EventArgs e)
{
var saveDialog = new SaveFileDialog
{
Filter = "PNG files (*.png)|*.png|JPEG files (*.jpg)|*.jpg",
Title = "保存图表"
};
if (saveDialog.ShowDialog() == DialogResult.OK)
{
formsPlot.Plot.Save(saveDialog.FileName, formsPlot.Width, formsPlot.Height);
MessageBox.Show("图表已保存", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
_refreshTimer?.Stop();
_deviceManager?.Dispose();
_monitor?.Dispose();
base.OnFormClosing(e);
}
}
}

🔧 核心技术点深度解析
🎯 1. 并发处理最佳实践
// ✅ 正确使用:ConcurrentDictionary保证线程安全
private readonly ConcurrentDictionary<string, IoTDevice> _devices;
// ❌ 错误用法:普通Dictionary在多线程环境下不安全
// private readonly Dictionary<string, IoTDevice> _devices;
// 🔥 性能优化:使用Parallel.ForEach并行处理
public void StartAllDevices()
{
Parallel.ForEach(_devices.Values, device => device.Start());
}
🎯 2. 内存管理策略
// 🚨 关键:限制数据点数量防止内存溢出
private const int MAX_DATA_POINTS = 100;
while (_tempData.Count > MAX_DATA_POINTS)
{
_tempData.TryDequeue(out _); // 移除旧数据
}
🎯 3. 异常处理机制
// 🔄 设备自动恢复机制
if (_random.NextDouble() < 0.02) // 2%概率故障
{
Status = DeviceStatus.Error;
}
else if (Status == DeviceStatus.Error && _random.NextDouble() < 0.1) // 10%概率恢复
{
Status = DeviceStatus.Online;
}
🎯 性能优化建议
🔥 内存优化
// 定期清理过期数据
private void CleanupOldData()
{
var cutoffTime = DateTime.Now.AddMinutes(-10);
while (_tempData.Count > 0 && _tempData.First().time < cutoffTime)
{
_tempData.TryDequeue(out _);
}
}
⚡ 处理速度优化
// 使用缓存减少重复计算
private readonly ConcurrentDictionary<string, double> _dataCache = new();
// 批量处理数据
public void ProcessBatch(List<DeviceData> dataList)
{
Parallel.ForEach(dataList, data => ProcessSingleData(data));
}
📊 关键指标监控
🎯 系统性能指标
🔍 业务指标
🎊 总结与展望
通过这个完整的IoT设备监控系统案例,我们学习了:
🔥 三大核心技术
并发安全处理
使用ConcurrentDictionary
和Parallel.ForEach
确保多线程环境下的数据安全实时数据可视化
性能监控优化
通过MetricsCollector
实现系统性能的实时追踪
💡 实战价值
阅读原文:原文链接
该文章在 2025/7/26 9:10:25 编辑过