【C#避坑实战系列文章14】如何让 C# 上位机软件运行更稳定?——5 个你必须知道的设计模式

C#上位机稳定的5大设计模式
#编程达人挑战赛·第1期#

做C#上位机开发久了,你一定遇到过这些“老大难”问题:设备通信模块改一行代码,整个界面跟着卡死;数据采集和UI更新抢资源,日志里满是“跨线程操作无效”的异常;项目迭代3次后,代码变成“意大利面”,新增一个传感器要改十几个类;现场运行时突然崩溃,排查发现是配置管理类被重复创建了5次……

这些问题看似是“bug”,实则是架构设计的缺失。上位机软件要实现“7×24小时稳定运行、数据不丢包、界面不卡顿、后期易维护”,靠的不是“写代码时细心点”,而是用设计模式搭建底层骨架。本文聚焦上位机开发中最实用的5个设计模式——工厂模式、观察者模式、MVVM模式、单例模式、异步消息队列模式,结合工业设备通信、数据采集、UI交互等真实场景,用“问题+方案+代码”的方式拆解其应用,帮你从“能跑通”进阶到“写得稳”。

一、先想清楚:上位机为什么需要设计模式?

上位机软件的核心诉求是**“稳定、可靠、可维护”**,而设计模式正是解决这些诉求的“通用模板”:

  • 解耦:避免“牵一发而动全身”,比如设备通信模块和UI模块分离,改通信逻辑不影响界面;
  • 复用:相同功能(如日志、配置)不用重复写代码,直接复用设计好的组件;
  • 抗并发:处理多线程数据采集、UI更新时,避免资源竞争和死锁;
  • 易维护:代码结构清晰,新人接手能快速理解,迭代新功能不用重构底层。

设计模式不是“炫技”,而是让上位机软件“经得起现场考验”的基础——那些运行几年不崩溃的工业上位机,背后一定藏着合理的设计模式应用。

二、实战:5个设计模式在上位机中的落地应用

1. 工厂模式:设备通信模块的“统一入口”

上位机痛点:项目中需要对接多种设备——串口传感器、网口PLC、USB工业相机,每种设备的通信逻辑不同(SerialPort、Socket、CameraSDK)。如果直接在代码里用if-else判断设备类型,新增设备时要修改大量代码,还容易引入bug。

工厂模式核心:定义一个“工厂接口”,每种设备对应一个“具体工厂”,通过工厂创建设备通信实例,上层代码只需调用统一接口,不用关心具体实现。

代码实现:设备通信工厂
using System;
using System.IO.Ports;

// 1. 定义设备通信接口(统一方法)
public interface IDeviceCommunicator
{
    bool Connect();       // 连接设备
    bool Disconnect();    // 断开连接
    byte[] ReadData();    // 读取数据
    bool WriteData(byte[] data); // 写入数据
}

// 2. 具体设备实现(串口设备)
public class SerialCommunicator : IDeviceCommunicator
{
    private SerialPort _serialPort;
    private string _portName;
    private int _baudRate;

    // 构造函数:传入串口参数
    public SerialCommunicator(string portName, int baudRate)
    {
        _portName = portName;
        _baudRate = baudRate;
        _serialPort = new SerialPort(portName, baudRate)
        {
            ReadTimeout = 1000,
            WriteTimeout = 1000
        };
    }

    public bool Connect()
    {
        try
        {
            if (!_serialPort.IsOpen)
                _serialPort.Open();
            return true;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"串口连接失败:{ex.Message}");
            return false;
        }
    }

    public bool Disconnect()
    {
        if (_serialPort.IsOpen)
            _serialPort.Close();
        return true;
    }

    public byte[] ReadData()
    {
        try
        {
            int bytesToRead = _serialPort.BytesToRead;
            if (bytesToRead == 0) return null;
            byte[] data = new byte[bytesToRead];
            _serialPort.Read(data, 0, bytesToRead);
            return data;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"串口读取失败:{ex.Message}");
            return null;
        }
    }

    public bool WriteData(byte[] data)
    {
        try
        {
            _serialPort.Write(data, 0, data.Length);
            return true;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"串口写入失败:{ex.Message}");
            return false;
        }
    }
}

// 3. 具体设备实现(网口PLC)
public class TcpCommunicator : IDeviceCommunicator
{
    private System.Net.Sockets.TCPClient _tcpClient;
    private string _ip;
    private int _port;

    public TcpCommunicator(string ip, int port)
    {
        _ip = ip;
        _port = port;
        _tcpClient = new System.Net.Sockets.TCPClient();
    }

    public bool Connect()
    {
        try
        {
            _tcpClient.Connect(_ip, _port);
            return true;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"TCP连接失败:{ex.Message}");
            return false;
        }
    }

    // 其他方法(Disconnect、ReadData、WriteData)实现略...
}

// 4. 设备工厂(根据类型创建通信实例)
public static class DeviceCommunicatorFactory
{
    public static IDeviceCommunicator CreateCommunicator(string deviceType, params object[] parameters)
    {
        return deviceType.ToLower() switch
        {
            "serial" => new SerialCommunicator((string)parameters[0], (int)parameters[1]),
            "tcp" => new TcpCommunicator((string)parameters[0], (int)parameters[1]),
            _ => throw new ArgumentException($"不支持的设备类型:{deviceType}")
        };
    }
}

// 5. 上层调用(不用关心具体设备类型)
public class DeviceManager
{
    private IDeviceCommunicator _communicator;

    public void InitDevice(string deviceType, params object[] parameters)
    {
        // 由工厂创建通信实例,上层只需调用接口方法
        _communicator = DeviceCommunicatorFactory.CreateCommunicator(deviceType, parameters);
        if (_communicator.Connect())
        {
            Console.WriteLine("设备连接成功!");
        }
    }

    public void ReadDeviceData()
    {
        byte[] data = _communicator.ReadData();
        if (data != null)
        {
            Console.WriteLine($"读取数据:{BitConverter.ToString(data)}");
        }
    }
}

优势

  • 新增设备(如USB相机)时,只需实现IDeviceCommunicator和对应的工厂分支,不用修改DeviceManager等上层代码;
  • 通信逻辑集中在具体实现类,代码职责清晰,调试时能快速定位问题(比如串口问题只看SerialCommunicator)。

2. 观察者模式:数据采集与UI更新的“解耦神器”

上位机痛点:数据采集线程读取传感器数据后,需要更新UI界面(仪表盘、数据表格)、触发告警模块、保存日志文件。如果采集线程直接调用UI、告警、日志的方法,会导致“采集模块”和“消费模块”强耦合,新增一个消费模块(如数据上传云平台)要改采集代码。

观察者模式核心:定义“被观察者”(数据采集器)和“观察者”(UI、告警、日志),被观察者数据变化时,自动通知所有观察者,观察者根据数据做出响应,两者通过接口解耦。

代码实现:数据采集的观察者模式
using System;
using System.Collections.Generic;

// 1. 定义观察者接口(接收数据通知)
public interface IDataObserver
{
    void OnDataReceived(byte[] data); // 数据接收通知
}

// 2. 定义被观察者接口(管理观察者、发送通知)
public interface IDataObservable
{
    void AddObserver(IDataObserver observer); // 添加观察者
    void RemoveObserver(IDataObserver observer); // 移除观察者
    void NotifyObservers(byte[] data); // 通知所有观察者
}

// 3. 被观察者实现(数据采集器)
public class DataCollector : IDataObservable
{
    private List<IDataObserver> _observers = new List<IDataObserver>();
    private IDeviceCommunicator _communicator;
    private System.Timers.Timer _collectTimer;

    public DataCollector(IDeviceCommunicator communicator)
    {
        _communicator = communicator;
        // 初始化采集定时器(100ms一次)
        _collectTimer = new System.Timers.Timer(100)
        {
            AutoReset = true,
            Enabled = false
        };
        _collectTimer.Elapsed += (s, e) => CollectData();
    }

    // 开始采集
    public void StartCollect()
    {
        if (_communicator.Connect())
        {
            _collectTimer.Start();
        }
    }

    // 停止采集
    public void StopCollect()
    {
        _collectTimer.Stop();
        _communicator.Disconnect();
    }

    // 采集数据并通知观察者
    private void CollectData()
    {
        byte[] data = _communicator.ReadData();
        if (data != null && data.Length > 0)
        {
            NotifyObservers(data); // 数据变化,通知所有观察者
        }
    }

    // 实现被观察者接口方法
    public void AddObserver(IDataObserver observer)
    {
        if (!_observers.Contains(observer))
        {
            _observers.Add(observer);
        }
    }

    public void RemoveObserver(IDataObserver observer)
    {
        if (_observers.Contains(observer))
        {
            _observers.Remove(observer);
        }
    }

    public void NotifyObservers(byte[] data)
    {
        foreach (var observer in _observers)
        {
            observer.OnDataReceived(data); // 调用每个观察者的通知方法
        }
    }
}

// 4. 观察者实现(UI更新模块)
public class UIDataObserver : IDataObserver
{
    private MainForm _mainForm;

    public UIDataObserver(MainForm mainForm)
    {
        _mainForm = mainForm;
    }

    public void OnDataReceived(byte[] data)
    {
        // 跨线程更新UI(假设数据是温湿度:前2字节温度,后2字节湿度)
        int temp = BitConverter.ToInt16(data, 0);
        int humi = BitConverter.ToInt16(data, 2);
        _mainForm.UpdateSensorData(temp, humi);
    }
}

// 5. 观察者实现(告警模块)
public class AlarmObserver : IDataObserver
{
    private int _maxTemp = 50; // 温度阈值

    public void OnDataReceived(byte[] data)
    {
        int temp = BitConverter.ToInt16(data, 0);
        if (temp > _maxTemp)
        {
            Console.WriteLine($"告警:温度超过阈值!当前{temp}℃");
            // 触发声光告警(代码略)
        }
    }
}

// 6. 上层调用(组装观察者和被观察者)
public class AppManager
{
    public void InitApp()
    {
        // 1. 工厂创建设备通信实例
        var communicator = DeviceCommunicatorFactory.CreateCommunicator("serial", "COM3", 9600);
        // 2. 创建被观察者(数据采集器)
        var dataCollector = new DataCollector(communicator);
        // 3. 创建观察者
        var uiObserver = new UIDataObserver(new MainForm());
        var alarmObserver = new AlarmObserver();
        // 4. 注册观察者
        dataCollector.AddObserver(uiObserver);
        dataCollector.AddObserver(alarmObserver);
        // 5. 开始采集
        dataCollector.StartCollect();
    }
}

优势

  • 采集模块只负责“采集和通知”,不关心谁用数据;消费模块只负责“接收和处理”,不关心数据从哪来;
  • 新增消费模块(如CloudUploadObserver上传云平台)时,只需实现IDataObserver并注册,不用修改采集代码;
  • 避免采集线程直接操作UI,减少“跨线程异常”风险。

3. MVVM模式:UI与业务逻辑的“彻底分离”

上位机痛点:用WinForm开发时,习惯把业务逻辑(如数据解析、设备控制)写在Form类里,导致Form类动辄上千行代码。修改一个按钮逻辑可能影响数据解析,UI布局调整要动业务代码,后期维护极其困难。

MVVM模式核心:将软件分为Model(数据模型)、View(视图/UI)、ViewModel(视图模型) 三层——View只负责展示,ViewModel处理业务逻辑并暴露数据接口,Model定义数据结构,三者通过数据绑定通信,实现“UI与业务完全解耦”。

代码实现:WPF上位机的MVVM模式(WinForm也可借鉴)
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;

// 1. Model(数据模型:传感器数据)
public class SensorDataModel : INotifyPropertyChanged
{
    private int _temperature;
    private int _humidity;

    // 温度属性(支持数据绑定)
    public int Temperature
    {
        get => _temperature;
        set
        {
            _temperature = value;
            OnPropertyChanged(); // 属性变化时通知UI
        }
    }

    // 湿度属性(支持数据绑定)
    public int Humidity
    {
        get => _humidity;
        set
        {
            _humidity = value;
            OnPropertyChanged();
        }
    }

    // 实现INotifyPropertyChanged接口(数据绑定必备)
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

// 2. ViewModel(视图模型:处理业务逻辑)
public class MainViewModel : INotifyPropertyChanged
{
    private SensorDataModel _sensorData;
    private DataCollector _dataCollector;

    // 暴露给View的数据模型(用于绑定)
    public SensorDataModel SensorData
    {
        get => _sensorData;
        set
        {
            _sensorData = value;
            OnPropertyChanged();
        }
    }

    // 命令(View按钮绑定此命令,触发业务逻辑)
    public RelayCommand StartCollectCommand { get; }
    public RelayCommand StopCollectCommand { get; }

    public MainViewModel()
    {
        // 初始化数据模型
        SensorData = new SensorDataModel();
        // 初始化数据采集器
        var communicator = DeviceCommunicatorFactory.CreateCommunicator("serial", "COM3", 9600);
        _dataCollector = new DataCollector(communicator);
        // 注册观察者:更新数据模型
        _dataCollector.AddObserver(new DataModelObserver(this));
        // 初始化命令
        StartCollectCommand = new RelayCommand(() => _dataCollector.StartCollect());
        StopCollectCommand = new RelayCommand(() => _dataCollector.StopCollect());
    }

    // 数据模型观察者:接收采集数据并更新ViewModel
    private class DataModelObserver : IDataObserver
    {
        private MainViewModel _viewModel;

        public DataModelObserver(MainViewModel viewModel)
        {
            _viewModel = viewModel;
        }

        public void OnDataReceived(byte[] data)
        {
            // 解析数据并更新ViewModel的SensorData
            _viewModel.SensorData.Temperature = BitConverter.ToInt16(data, 0);
            _viewModel.SensorData.Humidity = BitConverter.ToInt16(data, 2);
        }
    }

    // 实现INotifyPropertyChanged接口
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

// 3. View(视图:WPF界面,仅负责展示,无业务逻辑)
// MainWindow.xaml(UI代码):
/*
<Window x:Class="MvvmUpperMachine.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:vm="clr-namespace:MvvmUpperMachine"
        Title="MVVM上位机" Height="300" Width="400">
    <Window.DataContext>
        <!-- 绑定ViewModel -->
        <vm:MainViewModel />
    </Window.DataContext>
    <Grid Margin="20">
        <StackPanel>
            <!-- 温度显示:绑定ViewModel的SensorData.Temperature -->
            <TextBlock Text="温度:" FontSize="16"/>
            <TextBlock Text="{Binding SensorData.Temperature, StringFormat={} {0}℃}" FontSize="20" Margin="0,5"/>
            
            <!-- 湿度显示:绑定ViewModel的SensorData.Humidity -->
            <TextBlock Text="湿度:" FontSize="16" Margin="0,10"/>
            <TextBlock Text="{Binding SensorData.Humidity, StringFormat={} {0}%}" FontSize="20" Margin="0,5"/>
            
            <!-- 按钮:绑定ViewModel的命令 -->
            <StackPanel Orientation="Horizontal" Margin="0,20">
                <Button Content="开始采集" Command="{Binding StartCollectCommand}" Margin="0,0,10,0"/>
                <Button Content="停止采集" Command="{Binding StopCollectCommand}"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>
*/

// 4. 辅助类:RelayCommand(实现ICommand,用于按钮命令绑定)
public class RelayCommand : ICommand
{
    private readonly Action _execute;
    private readonly Func<bool> _canExecute;

    public event EventHandler CanExecuteChanged;

    public RelayCommand(Action execute, Func<bool> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute == null || _canExecute();
    }

    public void Execute(object parameter)
    {
        _execute();
    }

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

优势

  • View里没有一行业务代码,UI布局调整只需改XAML,不影响逻辑;
  • ViewModel专注业务逻辑,可单独进行单元测试(比如不启动UI,直接测试采集和解析逻辑);
  • 数据绑定自动同步UI和ViewModel,避免手动调用Invoke更新UI,减少跨线程bug。

4. 单例模式:全局资源的“唯一管理者”

上位机痛点:配置管理、日志服务、设备管理器这些全局组件,如果被重复创建多个实例,会导致“配置读取冲突”“日志文件被占用”“设备多次连接”等问题。比如配置类被创建两次,一次读了config1.ini,一次读了config2.ini,程序行为混乱。

单例模式核心:确保一个类只有一个实例,并提供全局唯一的访问入口,避免重复创建资源。

代码实现:线程安全的单例模式(以上位机配置管理为例)
using System;
using System.IO;

public class AppConfig
{
    // 1. 私有静态实例(volatile确保多线程下实例可见)
    private static volatile AppConfig _instance;
    // 2. 私有锁对象(保证线程安全)
    private static readonly object _lockObj = new object();
    // 3. 配置项
    public string SerialPortName { get; private set; }
    public int BaudRate { get; private set; }
    public string TcpIp { get; private set; }
    public int TcpPort { get; private set; }

    // 4. 私有构造函数(禁止外部new)
    private AppConfig()
    {
        // 从配置文件加载配置(只加载一次)
        LoadConfig();
    }

    // 5. 公共静态方法:获取唯一实例(双重锁定保证线程安全)
    public static AppConfig Instance
    {
        get
        {
            if (_instance == null) // 第一重判断(避免每次加锁)
            {
                lock (_lockObj) // 加锁
                {
                    if (_instance == null) // 第二重判断(确保只创建一次)
                    {
                        _instance = new AppConfig();
                    }
                }
            }
            return _instance;
        }
    }

    // 加载配置文件(如app.config或自定义ini文件)
    private void LoadConfig()
    {
        try
        {
            // 示例:从ini文件读取配置(实际项目可使用ConfigurationManager)
            var configLines = File.ReadAllLines("app.ini");
            foreach (var line in configLines)
            {
                if (line.StartsWith("SerialPortName:"))
                    SerialPortName = line.Split(':')[1].Trim();
                else if (line.StartsWith("BaudRate:"))
                    BaudRate = int.Parse(line.Split(':')[1].Trim());
                else if (line.StartsWith("TcpIp:"))
                    TcpIp = line.Split(':')[1].Trim();
                else if (line.StartsWith("TcpPort:"))
                    TcpPort = int.Parse(line.Split(':')[1].Trim());
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"加载配置失败:{ex.Message},使用默认配置");
            // 加载失败时使用默认配置
            SerialPortName = "COM3";
            BaudRate = 9600;
            TcpIp = "192.168.1.100";
            TcpPort = 502;
        }
    }

    // 保存配置(全局唯一实例,避免多实例冲突)
    public void SaveConfig()
    {
        var configContent = $"SerialPortName:{SerialPortName}\n" +
                           $"BaudRate:{BaudRate}\n" +
                           $"TcpIp:{TcpIp}\n" +
                           $"TcpPort:{TcpPort}";
        File.WriteAllText("app.ini", configContent);
    }
}

// 上层调用(全局唯一访问)
public class ConfigUsageDemo
{
    public void TestConfig()
    {
        // 无论在哪调用,都是同一个实例
        var config1 = AppConfig.Instance;
        var config2 = AppConfig.Instance;
        Console.WriteLine(ReferenceEquals(config1, config2)); // 输出True(同一实例)

        // 使用配置创建设备通信
        var serialComm = DeviceCommunicatorFactory.CreateCommunicator(
            "serial", config1.SerialPortName, config1.BaudRate);
        var tcpComm = DeviceCommunicatorFactory.CreateCommunicator(
            "tcp", config1.TcpIp, config1.TcpPort);
    }
}

优势

  • 全局组件(配置、日志、设备管理)只有一个实例,避免资源竞争和配置冲突;
  • 线程安全的实现(双重锁定)确保多线程环境下不会创建多个实例;
  • 配置加载只执行一次,提高程序启动效率。

5. 异步消息队列模式:高频数据的“削峰填谷”

上位机痛点:当传感器数据采集频率很高(如每秒1000次),直接在采集线程处理数据(解析、存储、UI更新)会导致“数据堆积”——采集速度超过处理速度,最终程序卡顿甚至崩溃。

异步消息队列模式核心:用一个“线程安全的队列”作为缓冲,采集线程(生产者)只负责将数据放入队列,处理线程(消费者)从队列中取出数据异步处理,实现“生产和消费解耦”,削峰填谷。

代码实现:基于ConcurrentQueue的异步消息队列
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

public class AsyncMessageQueue<T>
{
    // 线程安全的队列(ConcurrentQueue是.NET内置的线程安全队列)
    private readonly ConcurrentQueue<T> _queue = new ConcurrentQueue<T>();
    // 取消令牌(用于停止消费者线程)
    private readonly CancellationTokenSource _cts = new CancellationTokenSource();
    // 消费者处理函数(由外部传入)
    private readonly Func<T, Task> _processFunc;
    // 消费者任务(异步线程)
    private Task _consumerTask;

    // 构造函数:传入数据处理函数
    public AsyncMessageQueue(Func<T, Task> processFunc)
    {
        _processFunc = processFunc ?? throw new ArgumentNullException(nameof(processFunc));
    }

    // 启动消费者(开始处理队列数据)
    public void StartConsumer()
    {
        _consumerTask = Task.Run(async () => await ConsumerLoop(), _cts.Token);
    }

    // 停止消费者
    public async Task StopConsumerAsync()
    {
        _cts.Cancel(); // 发送取消信号
        if (_consumerTask != null)
        {
            await _consumerTask; // 等待消费者线程结束
        }
    }

    // 生产者:将数据放入队列
    public void Enqueue(T data)
    {
        if (_cts.Token.IsCancellationRequested)
            throw new InvalidOperationException("队列已停止接收数据");
        _queue.Enqueue(data); // 线程安全的入队操作
    }

    // 消费者循环:不断从队列取数据处理
    private async Task ConsumerLoop()
    {
        while (!_cts.Token.IsCancellationRequested)
        {
            try
            {
                // 尝试从队列取数据(10ms超时,避免空等浪费CPU)
                if (_queue.TryDequeue(out T data))
                {
                    // 异步处理数据(不阻塞队列取数据)
                    await _processFunc(data);
                }
                else
                {
                    // 队列空时,短暂等待
                    await Task.Delay(10, _cts.Token);
                }
            }
            catch (OperationCanceledException)
            {
                // 取消操作,退出循环
                break;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"消费者处理数据失败:{ex.Message}");
            }
        }
    }
}

// 上位机中的应用(高频数据采集场景)
public class HighFreqDataManager
{
    private AsyncMessageQueue<byte[]> _dataQueue;
    private DataCollector _dataCollector;

    public void InitHighFreqCollect()
    {
        // 1. 创建消息队列:指定数据处理函数(异步处理)
        _dataQueue = new AsyncMessageQueue<byte[]>(ProcessDataAsync);
        // 2. 启动消费者线程
        _dataQueue.StartConsumer();
        // 3. 创建数据采集器,注册观察者(将数据入队)
        var communicator = DeviceCommunicatorFactory.CreateCommunicator("serial", "COM3", 115200);
        _dataCollector = new DataCollector(communicator);
        _dataCollector.AddObserver(new QueueDataObserver(this));
        // 4. 开始高频采集(每秒1000次)
        _dataCollector.StartCollect();
    }

    // 观察者:将采集到的数据放入队列
    private class QueueDataObserver : IDataObserver
    {
        private HighFreqDataManager _manager;

        public QueueDataObserver(HighFreqDataManager manager)
        {
            _manager = manager;
        }

        public void OnDataReceived(byte[] data)
        {
            _manager._dataQueue.Enqueue(data); // 生产者入队
        }
    }

    // 数据处理函数(异步,消费者调用)
    private async Task ProcessDataAsync(byte[] data)
    {
        try
        {
            // 1. 解析数据(异步操作,如复杂计算)
            var sensorData = await ParseDataAsync(data);
            // 2. 保存到数据库(异步IO操作)
            await SaveToDatabaseAsync(sensorData);
            // 3. 更新UI(通过Dispatcher异步更新)
            UpdateUiAsync(sensorData);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"数据处理失败:{ex.Message}");
        }
    }

    // 模拟异步数据解析
    private Task<(int Temp, int Humi)> ParseDataAsync(byte[] data)
    {
        return Task.Run(() =>
        {
            int temp = BitConverter.ToInt16(data, 0);
            int humi = BitConverter.ToInt16(data, 2);
            return (temp, humi);
        });
    }

    // 模拟异步保存数据库
    private Task SaveToDatabaseAsync((int Temp, int Humi) data)
    {
        // 实际项目中使用EF Core等ORM的异步方法
        return Task.Delay(5); // 模拟IO耗时
    }

    // 异步更新UI
    private void UpdateUiAsync((int Temp, int Humi) data)
    {
        // WPF用Dispatcher,WinForm用Invoke
        Application.Current.Dispatcher.Invoke(() =>
        {
            // 更新UI代码略
        });
    }
}

优势

  • 采集线程(生产者)只做“入队”这一轻量操作,不会被耗时的处理逻辑阻塞,确保数据不丢失;
  • 处理线程(消费者)异步处理数据,即使处理速度慢,也只会在队列中堆积,不会导致采集线程卡死;
  • ConcurrentQueue是线程安全的,避免多线程操作队列的锁竞争问题。

三、讨论:你在上位机开发中用对设计模式了吗?

设计模式不是“银弹”,但它是解决上位机“稳定性、可维护性”问题的有效工具。或许你曾在项目中“无意识”用过设计模式——比如用一个静态类管理配置(单例的雏形),用事件通知UI更新(观察者的简化版)。

但真正的架构能力,是“有意识地根据问题选择合适的模式”:

  • 当需要统一管理多种设备通信时,用工厂模式
  • 当需要解耦数据生产者和消费者时,用观察者模式
  • 当需要分离UI和业务逻辑时,用MVVM模式
  • 当需要全局唯一资源时,用单例模式
  • 当需要处理高频数据时,用异步消息队列模式

如果本文的设计模式案例对您有帮助,欢迎在评论区留言讨论:

  • 您在工业上位机开发中遇到过哪些稳定性问题?
  • 您对异步消息队列或MVVM模式有哪些疑问?
    我会从优质评论中抽取5位读者,免费赠送《C#设计模式实战资料》!

------------伴代码深耕技术、连万物探索物联,我聚焦计算机、物联网与上位机领域,盼同频的你关注,一起交流成长~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码畔星连

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值