目录

C上位机生产者消费者模式

三大组件:

BlockingCollection

生产者消费者模式注意点:

创建生产者和消费者

解释:

 模拟上位机实际应用:数据采集系统

使用Channel实现(.NET Core 3.0+ 推荐)

 上位机多生产者多消费者模式

上位机WinForms实际应用

生产者消费者模式要点总结 📝

三大组件:

四种实现方式对比:

核心优势:

常见问题及解决方案:

上位机应用场景:

记忆口诀:

生产者与消费者中数据共享与单队列

情况1:数据只能被一个消费者使用(最常见)

情况2:多个消费者共享同一份数据

情况3:每个消费者都有自己的队列

2. 上位机实际应用场景

场景A:数据处理流水线(数据只能被一个消费者使用)

场景B:数据广播系统(多个消费者使用相同数据)

3. 解决方案对比表

4. 代码示例:如何实现数据共享

方法1:使用.NET的Dataflow库(推荐)

方法2:使用响应式扩展(Rx.NET)

方法3:手动实现共享队列

5. 上位机实际选择建议

什么时候用单消费者模式?

什么时候用多消费者共享模式?

6. 常见问题解答

Q1:如果我想让多个消费者都能处理同一份数据,但希望它们独立处理不相互影响,怎么办?

Q2:如何保证数据处理的顺序性?

7. 总结


C上位机生产者消费者模式

生产者-消费者模式是一种经典的设计模式,它将数据的生成(生产者)和处理(消费者)分离到不同的模块或线程中。这种模式的核心在于一个共享的缓冲区,生产者将数据放入缓冲区,而消费者从缓冲区中取出数据进行处理。这种模式有助于提高系统的响应性和吞吐量,因为它允许生产者和消费者并行工作,互不干扰。

三大组件:

  1. 生产者(Producer):产生数据

  2. 缓冲区(Buffer):存放数据(队列)

  3. 消费者(Consumer):处理数据

BlockingCollection<T>

  • BlockingCollection是生产者消费者模式的核心,它提供了一个线程安全的集合,用于生产者和消费者之间的通信。它支持阻塞和超时操作,这使得实现生产者消费者模式变得简单而高效。
  • BlockingCollection<T> 是 C中定义在 System.Collections.Concurrent 命名空间下的一个线程安全集合类,主要用于简化多线程编程中的数据共享和同步问题。
  • 核心功能上,它主要用于实现生产者-消费者模式, 允许多个生产者线程安全地添加数据,同时多个消费者线程安全地移除数据;当集合达到指定最大容量时,添加操作会自动阻塞(等待空间),而当集合为空时,移除操作也会阻塞(等待数据),从而避免忙等待并高效利用资源。
  • 关键特性包括: 线程安全(内部处理同步,允许多线程并发操作而无需额外锁)、可选容量限制(支持固定容量队列,通过阻塞实现流量控制)、以及完成通知机制(通过 CompleteAdding 方法标记添加完成,IsCompleted 属性供消费者检测终止条件)。
  • 典型应用场景涵盖: 多线程数据处理流水线(如文件读取、处理、存储)、任务调度系统、以及任何需要缓冲或协调生产与消费速度的场景。

生产者消费者模式注意点:

正常情况下生产者数据只能被一个消费者使用,但也可以通过事件通知或者广播的形式将数据共享给所有消费者。

解决方案对比表

方案 数据是否共享 优点 缺点 适用场景
传统单队列 ❌ 不共享 简单高效,节省内存 数据只能处理一次 流水线处理,任务分发
广播模式 ✅ 共享 所有消费者获得相同数据 消费者可能阻塞广播 监控数据多界面显示
多队列复制 ✅ 共享 消费者独立,互不干扰 内存消耗大,需要深拷贝 需要独立处理的场景
发布-订阅 ✅ 共享 高度解耦,灵活扩展 复杂度较高 复杂事件处理系统

共享模式内容在文章最后面

创建生产者和消费者

首先,定义生产者和消费者的类:

class Program
{
    static void Main(string[] args)
    {
        // 创建一个BlockingCollection实例
        BlockingCollection<int> queue = new BlockingCollection<int>();

        // 启动生产者和消费者任务
        Task producer = Task.Run(() => Producer(queue));
        Task consumer = Task.Run(() => Consumer(queue));

        // 等待所有任务完成
        Task.WaitAll(producer, consumer);

        // 完成所有操作后,添加一个空项到队列中,以通知消费者任务完成
        queue.CompleteAdding();
    }

    static void Producer(BlockingCollection<int> queue)
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("Produced: " + i);
            queue.Add(i); // 添加元素到队列中
            Thread.Sleep(100); // 模拟耗时操作
        }
    }

    static void Consumer(BlockingCollection<int> queue)
    {
        foreach (int item in queue.GetConsumingEnumerable()) // 循环获取并处理元素,直到队列为空或完成添加标记被调用
        {
            Console.WriteLine("Consumed: " + item);
            Thread.Sleep(200); // 模拟耗时操作
        }
    }
}
解释:
  • Producer‌:生产者负责生成数据并添加到BlockingCollection中。这里我们简单地生成了0到9的数字。
  • Consumer‌:消费者从BlockingCollection中取出数据并处理。这里使用了GetConsumingEnumerable()方法,这个方法会阻塞直到有元素可用或者集合完成添加(调用CompleteAdding())。
  • BlockingCollection‌:这是一个线程安全的队列,支持阻塞操作。当队列为空时,GetConsumingEnumerable()会阻塞,直到有新的元素被添加到队列中。当所有元素都被消费并且调用了CompleteAdding()方法后,它会停止阻塞,允许消费者知道没有更多的数据会被添加。

 模拟上位机实际应用:数据采集系统

场景描述
PLC/传感器 → 数据采集 → 数据队列 → 界面显示/数据保存/报警检查

csharp
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Collections.Generic;

namespace DataAcquisitionSystem
{
    // 数据结构
    public class SensorData
    {
        public DateTime Timestamp { get; set; }
        public int DeviceId { get; set; }
        public string SensorType { get; set; }
        public double Value { get; set; }
        public string Unit { get; set; }
        
        public override string ToString()
        {
            return $"[{Timestamp:HH:mm:ss.fff}] 设备{DeviceId} {SensorType}: {Value:F2}{Unit}";
        }
    }
    
    // 生产者:模拟硬件数据采集
    public class DataProducer
    {
        private BlockingCollection<SensorData> _dataQueue;
        private CancellationTokenSource _cancellationTokenSource;
        private System.Timers.Timer _timer;
        private Random _random = new Random();
        private int _dataCount = 0;
        
        public DataProducer(BlockingCollection<SensorData> queue)
        {
            _dataQueue = queue;
            _cancellationTokenSource = new CancellationTokenSource();
        }
        
        public void Start()
        {
            Console.WriteLine("🔌 数据采集器:启动");
            
            // 使用定时器模拟硬件数据采集
            _timer = new System.Timers.Timer(100); // 100ms采集一次
            _timer.Elapsed += OnTimerElapsed;
            _timer.Start();
        }
        
        public void Stop()
        {
            Console.WriteLine("🔌 数据采集器:停止");
            _timer?.Stop();
            _timer?.Dispose();
            _cancellationTokenSource.Cancel();
        }
        
        private void OnTimerElapsed(object sender, ElapsedEventArgs e)
        {
            try
            {
                // 模拟多个传感器数据
                for (int i = 1; i <= 3; i++) // 3个设备
                {
                    var data = new SensorData
                    {
                        Timestamp = DateTime.Now,
                        DeviceId = i,
                        SensorType = GetSensorType(i),
                        Value = GenerateSensorValue(i),
                        Unit = GetUnit(i)
                    };
                    
                    // 添加到队列(非阻塞,如果队列满会丢弃最旧数据)
                    if (!_dataQueue.TryAdd(data))
                    {
                        Console.WriteLine("⚠️  队列已满,丢弃数据");
                    }
                    else
                    {
                        Interlocked.Increment(ref _dataCount);
                        Console.WriteLine($"📡 采集:{data}");
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"❌ 采集错误:{ex.Message}");
            }
        }
        
        private string GetSensorType(int deviceId)
        {
            return deviceId switch
            {
                1 => "温度",
                2 => "压力",
                3 => "流量",
                _ => "未知"
            };
        }
        
        private double GenerateSensorValue(int deviceId)
        {
            return deviceId switch
            {
                1 => 20 + _random.NextDouble() * 10,  // 20-30°C
                2 => 100 + _random.NextDouble() * 50, // 100-150 kPa
                3 => 10 + _random.NextDouble() * 5,   // 10-15 L/min
                _ => 0
            };
        }
        
        private string GetUnit(int deviceId)
        {
            return deviceId switch
            {
                1 => "°C",
                2 => "kPa",
                3 => "L/min",
                _ => ""
            };
        }
    }
    
    // 消费者1:数据显示器
    public class DataDisplayConsumer
    {
        private BlockingCollection<SensorData> _dataQueue;
        private CancellationToken _cancellationToken;
        private Task _consumerTask;
        
        public DataDisplayConsumer(BlockingCollection<SensorData> queue, CancellationToken token)
        {
            _dataQueue = queue;
            _cancellationToken = token;
        }
        
        public void Start()
        {
            Console.WriteLine("📺 数据显示器:启动");
            _consumerTask = Task.Run(() => ConsumeData());
        }
        
        public async Task StopAsync()
        {
            Console.WriteLine("📺 数据显示器:停止中...");
            await _consumerTask;
        }
        
        private void ConsumeData()
        {
            try
            {
                foreach (var data in _dataQueue.GetConsumingEnumerable(_cancellationToken))
                {
                    // 模拟UI显示(这里简化,实际应该用Control.Invoke)
                    DisplayOnUI(data);
                    
                    // 控制处理速度,避免UI卡顿
                    Thread.Sleep(50);
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("📺 数据显示器:已取消");
            }
        }
        
        private void DisplayOnUI(SensorData data)
        {
            // 这里应该用Control.Invoke更新UI
            Console.WriteLine($"🖥️  显示:{data}");
        }
    }
    
    // 消费者2:数据存储器
    public class DataStorageConsumer
    {
        private BlockingCollection<SensorData> _dataQueue;
        private CancellationToken _cancellationToken;
        private List<SensorData> _storage = new List<SensorData>();
        private Task _consumerTask;
        
        public DataStorageConsumer(BlockingCollection<SensorData> queue, CancellationToken token)
        {
            _dataQueue = queue;
            _cancellationToken = token;
        }
        
        public void Start()
        {
            Console.WriteLine("💾 数据存储器:启动");
            _consumerTask = Task.Run(() => ConsumeData());
        }
        
        public async Task StopAsync()
        {
            Console.WriteLine("💾 数据存储器:停止中...");
            await _consumerTask;
        }
        
        private void ConsumeData()
        {
            try
            {
                foreach (var data in _dataQueue.GetConsumingEnumerable(_cancellationToken))
                {
                    // 保存到内存(实际应该保存到数据库或文件)
                    lock (_storage)
                    {
                        _storage.Add(data);
                    }
                    
                    // 每10条数据打印一次统计
                    if (_storage.Count % 10 == 0)
                    {
                        Console.WriteLine($"💾 已存储 {_storage.Count} 条数据");
                    }
                    
                    // 模拟存储时间
                    Thread.Sleep(30);
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("💾 数据存储器:已取消");
                SaveToFile(); // 停止时保存到文件
            }
        }
        
        private void SaveToFile()
        {
            Console.WriteLine($"💾 正在保存 {_storage.Count} 条数据到文件...");
            // 实际应该保存到文件或数据库
            Thread.Sleep(1000);
            Console.WriteLine("💾 数据保存完成");
        }
    }
    
    // 消费者3:报警检查器
    public class AlarmCheckerConsumer
    {
        private BlockingCollection<SensorData> _dataQueue;
        private CancellationToken _cancellationToken;
        private Dictionary<string, (double min, double max)> _alarmLimits;
        private Task _consumerTask;
        
        public AlarmCheckerConsumer(BlockingCollection<SensorData> queue, CancellationToken token)
        {
            _dataQueue = queue;
            _cancellationToken = token;
            
            // 设置报警阈值
            _alarmLimits = new Dictionary<string, (double min, double max)>
            {
                { "温度", (18, 35) },
                { "压力", (90, 180) },
                { "流量", (8, 20) }
            };
        }
        
        public void Start()
        {
            Console.WriteLine("🚨 报警检查器:启动");
            _consumerTask = Task.Run(() => ConsumeData());
        }
        
        public async Task StopAsync()
        {
            Console.WriteLine("🚨 报警检查器:停止中...");
            await _consumerTask;
        }
        
        private void ConsumeData()
        {
            try
            {
                foreach (var data in _dataQueue.GetConsumingEnumerable(_cancellationToken))
                {
                    CheckAlarm(data);
                    
                    // 模拟检查时间
                    Thread.Sleep(20);
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("🚨 报警检查器:已取消");
            }
        }
        
        private void CheckAlarm(SensorData data)
        {
            if (_alarmLimits.TryGetValue(data.SensorType, out var limits))
            {
                if (data.Value < limits.min)
                {
                    Console.WriteLine($"⚠️  低报警:{data.SensorType} {data.Value:F2}{data.Unit} < {limits.min}{data.Unit}");
                }
                else if (data.Value > limits.max)
                {
                    Console.WriteLine($"🚨 高报警:{data.SensorType} {data.Value:F2}{data.Unit} > {limits.max}{data.Unit}");
                }
            }
        }
    }
    
    // 主程序
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("🏭 上位机数据采集系统 - 生产者消费者模式\n");
            
            // 1. 创建数据队列(缓冲区)
            // 容量100,队列满时阻塞生产者
            BlockingCollection<SensorData> dataQueue = new BlockingCollection<SensorData>(100);
            
            // 2. 创建取消令牌(用于优雅停止)
            CancellationTokenSource cts = new CancellationTokenSource();
            
            // 3. 创建生产者和消费者
            var producer = new DataProducer(dataQueue);
            var displayConsumer = new DataDisplayConsumer(dataQueue, cts.Token);
            var storageConsumer = new DataStorageConsumer(dataQueue, cts.Token);
            var alarmConsumer = new AlarmCheckerConsumer(dataQueue, cts.Token);
            
            // 4. 启动所有组件
            producer.Start();
            displayConsumer.Start();
            storageConsumer.Start();
            alarmConsumer.Start();
            
            Console.WriteLine("\n⏰ 系统运行中(10秒后停止)...\n");
            
            // 5. 运行10秒
            await Task.Delay(10000);
            
            // 6. 优雅停止
            Console.WriteLine("\n🛑 正在停止系统...");
            
            // 先停止生产者
            producer.Stop();
            
            // 标记队列完成,通知消费者
            dataQueue.CompleteAdding();
            
            // 等待消费者完成
            await Task.WhenAll(
                displayConsumer.StopAsync(),
                storageConsumer.StopAsync(),
                alarmConsumer.StopAsync()
            );
            
            Console.WriteLine("\n✅ 系统已安全停止");
            Console.WriteLine($"📊 队列剩余数据:{dataQueue.Count} 条");
        }
    }
}

使用Channel实现(.NET Core 3.0+ 推荐)

C# Channel 是 .NET Core 3.0 及更高版本引入的一个线程安全的异步通信机制,位于 System.Threading.Channels 命名空间下,主要用于实现‌生产者-消费者模式‌,支持高效的数据传递和并发控制。‌

‌核心功能与特点:‌ Channel 本质上是一个高性能的队列,允许生产者和消费者在不同线程或任务间安全交换数据,其关键特性包括:

‌异步优先‌:通过 WriteAsync 和 ReadAsync 等方法原生支持 async/await,避免阻塞线程。
‌线程安全‌:内部机制确保多线程操作时数据一致性。
‌背压控制‌:支持有界(:ml-search-more[Bounded Channel]{text="Bounded Channel"})和无界(Unbounded Channel)通道,有界通道可通过策略(如 Wait、`DropNewest)处理数据积压。
‌读写分离‌:通过 ChannelWriter 和 ChannelReader 接口实现生产者与消费者的解耦。‌
‌典型应用场景:‌ Channel 适用于需要高并发、低延迟数据处理的场景,例如:

  • 实时消息处理
  • 任务管道
  • 微服务内部通信
  • 模拟消息队列(如RabbitMQ的轻量级替代)

但需注意,Channel 是进程内通信机制,不支持跨项目共享,与分布式消息中间件有本质区别。‌

以下实现:

using System;
using System.Threading.Channels;
using System.Threading.Tasks;

public class ChannelProducerConsumer
{
    static async Task Main()
    {
        Console.WriteLine("📡 Channel实现生产者消费者\n");
        
        // 1. 创建Channel(可设置容量和模式)
        var channel = Channel.CreateBounded<int>(new BoundedChannelOptions(10)
        {
            FullMode = BoundedChannelFullMode.Wait // 满了就等待
        });
        
        // 2. 启动生产者和消费者
        var producerTask = ProduceAsync(channel.Writer);
        var consumerTask = ConsumeAsync(channel.Reader);
        
        // 3. 等待完成
        await Task.WhenAll(producerTask, consumerTask);
        
        Console.WriteLine("\n✅ 完成!");
    }
    
    static async Task ProduceAsync(ChannelWriter<int> writer)
    {
        var random = new Random();
        
        for (int i = 1; i <= 20; i++)
        {
            var data = random.Next(100, 1000);
            
            await writer.WriteAsync(data);
            Console.WriteLine($"📦 生产:{data}");
            
            await Task.Delay(random.Next(100, 300));
        }
        
        writer.Complete(); // 标记完成
        Console.WriteLine("🏁 生产者完成");
    }
    
    static async Task ConsumeAsync(ChannelReader<int> reader)
    {
        await foreach (var data in reader.ReadAllAsync())
        {
            Console.WriteLine($"📥 消费:处理数据 {data}");
            await Task.Delay(200);
        }
        
        Console.WriteLine("🏁 消费者完成");
    }
}

 上位机多生产者多消费者模式

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

public class MultiProducerConsumer
{
    // 支持多个数据源和多个处理器的场景
    class MultiDataProcessor
    {
        private BlockingCollection<string> _queue = new BlockingCollection<string>(100);
        private CancellationTokenSource _cts = new CancellationTokenSource();
        private Task[] _producers;
        private Task[] _consumers;
        
        public void Start(int producerCount, int consumerCount)
        {
            Console.WriteLine($"🚀 启动 {producerCount} 个生产者,{consumerCount} 个消费者\n");
            
            // 启动多个生产者
            _producers = new Task[producerCount];
            for (int i = 0; i < producerCount; i++)
            {
                int id = i + 1;
                _producers[i] = Task.Run(() => Producer(id));
            }
            
            // 启动多个消费者
            _consumers = new Task[consumerCount];
            for (int i = 0; i < consumerCount; i++)
            {
                int id = i + 1;
                _consumers[i] = Task.Run(() => Consumer(id));
            }
        }
        
        public async Task StopAsync()
        {
            Console.WriteLine("\n🛑 停止所有生产者和消费者...");
            
            // 停止生产者
            _cts.Cancel();
            await Task.WhenAll(_producers);
            
            // 标记队列完成
            _queue.CompleteAdding();
            
            // 等待消费者处理完剩余数据
            await Task.WhenAll(_consumers);
            
            Console.WriteLine($"📊 最终队列大小:{_queue.Count}");
        }
        
        private async Task Producer(int id)
        {
            var random = new Random();
            int count = 1;
            
            while (!_cts.IsCancellationRequested)
            {
                try
                {
                    var data = $"P{id}-数据{count}";
                    _queue.Add(data);
                    Console.WriteLine($"📦 生产者{id}:{data}");
                    
                    count++;
                    await Task.Delay(random.Next(200, 500));
                }
                catch (OperationCanceledException)
                {
                    break;
                }
            }
            
            Console.WriteLine($"🏁 生产者{id} 已停止");
        }
        
        private async Task Consumer(int id)
        {
            foreach (var data in _queue.GetConsumingEnumerable())
            {
                Console.WriteLine($"📥 消费者{id}:处理 {data}");
                await Task.Delay(300); // 模拟处理时间
            }
            
            Console.WriteLine($"🏁 消费者{id} 已停止");
        }
    }
    
    static async Task Main()
    {
        var processor = new MultiDataProcessor();
        
        // 启动3个生产者,2个消费者
        processor.Start(3, 2);
        
        // 运行5秒
        await Task.Delay(5000);
        
        // 优雅停止
        await processor.StopAsync();
        
        Console.WriteLine("\n✅ 系统已停止");
    }
}

上位机WinForms实际应用

// WinForms主窗体代码
public partial class MainForm : Form
{
    private BlockingCollection<SensorData> _dataQueue;
    private DataProducer _producer;
    private CancellationTokenSource _cts;
    private Task _displayTask;
    
    public MainForm()
    {
        InitializeComponent();
        InitializeProducerConsumer();
    }
    
    private void InitializeProducerConsumer()
    {
        // 1. 创建队列(容量200)
        _dataQueue = new BlockingCollection<SensorData>(200);
        
        // 2. 创建生产者
        _producer = new DataProducer(_dataQueue);
        
        // 3. 创建取消令牌
        _cts = new CancellationTokenSource();
        
        // 4. 启动数据展示消费者
        StartDisplayConsumer();
    }
    
    private void StartDisplayConsumer()
    {
        _displayTask = Task.Run(async () =>
        {
            foreach (var data in _dataQueue.GetConsumingEnumerable(_cts.Token))
            {
                // 在UI线程上更新控件
                this.Invoke(new Action(() =>
                {
                    UpdateUI(data);
                }));
                
                await Task.Delay(50); // 控制UI更新频率
            }
        });
    }
    
    private void UpdateUI(SensorData data)
    {
        // 更新DataGridView
        dataGridView1.Rows.Insert(0, 
            data.Timestamp.ToString("HH:mm:ss.fff"),
            $"设备{data.DeviceId}",
            data.SensorType,
            $"{data.Value:F2}",
            data.Unit);
        
        // 保持最多1000行
        if (dataGridView1.Rows.Count > 1000)
            dataGridView1.Rows.RemoveAt(dataGridView1.Rows.Count - 1);
        
        // 更新图表
        UpdateChart(data);
        
        // 更新状态栏
        lblStatus.Text = $"最新数据:{data.SensorType} {data.Value:F2}{data.Unit}";
        lblQueueCount.Text = $"队列:{_dataQueue.Count}";
    }
    
    private void UpdateChart(SensorData data)
    {
        // 根据设备ID更新对应的图表系列
        string seriesName = $"设备{data.DeviceId}";
        
        if (!chart1.Series.Contains(seriesName))
        {
            var series = new Series(seriesName)
            {
                ChartType = SeriesChartType.Line
            };
            chart1.Series.Add(series);
        }
        
        chart1.Series[seriesName].Points.AddY(data.Value);
        
        // 保持合理的数据点数
        if (chart1.Series[seriesName].Points.Count > 200)
            chart1.Series[seriesName].Points.RemoveAt(0);
    }
    
    private void btnStart_Click(object sender, EventArgs e)
    {
        _producer.Start();
        btnStart.Enabled = false;
        btnStop.Enabled = true;
    }
    
    private async void btnStop_Click(object sender, EventArgs e)
    {
        _producer.Stop();
        _dataQueue.CompleteAdding();
        
        // 等待消费者完成
        await _displayTask;
        
        btnStart.Enabled = true;
        btnStop.Enabled = false;
    }
    
    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        // 窗体关闭时优雅停止
        _cts?.Cancel();
        _producer?.Stop();
        base.OnFormClosing(e);
    }
}


生产者消费者模式要点总结 📝

三大组件:

  1. 生产者(Producer):产生数据

  2. 缓冲区(Buffer):存放数据(队列)

  3. 消费者(Consumer):处理数据

四种实现方式对比:

方式 优点 缺点 适用场景
BlockingCollection 简单易用,线程安全 .NET Framework 4+ 大多数场景
Channel 性能好,API现代化 .NET Core 3.0+ 新项目推荐
手动锁+队列 完全控制 代码复杂 特殊需求
TPL Dataflow 功能强大 学习成本高 复杂数据流

核心优势:

  1. 解耦:生产者和消费者独立工作

  2. 缓冲:平滑处理速度差异

  3. 并发:充分利用多核CPU

  4. 可扩展:容易增加生产者或消费者

常见问题及解决方案:

// 问题1:队列满了怎么办?
var options = new BoundedChannelOptions(100)
{
    FullMode = BoundedChannelFullMode.Wait,     // 等待
    // FullMode = BoundedChannelFullMode.DropOldest, // 丢弃最旧
    // FullMode = BoundedChannelFullMode.DropNewest  // 丢弃最新
};

// 问题2:如何优雅停止?
// 1. 取消令牌通知生产者停止
// 2. CompleteAdding()标记队列完成
// 3. 等待消费者处理完剩余数据

// 问题3:消费者处理速度慢?
// 方案1:增加消费者数量
// 方案2:批量处理数据
// 方案3:异步处理,不阻塞队列

上位机应用场景:

  1. 数据采集系统:硬件→队列→显示/保存

  2. 日志系统:日志产生→队列→文件/网络

  3. 图像处理:摄像头→队列→识别算法

  4. 通信系统:网络接收→队列→协议解析

  5. 批量任务:任务产生→队列→并行处理

记忆口诀:

生产者只管产,
消费者只管干。
中间队列做缓冲,
速度不同也无妨。
多线程,不打架,
上位机里用处大。
硬件数据入队列,
界面显示不卡顿。


生产者与消费者中数据共享与单队列

情况1:数据只能被一个消费者使用(最常见)

// 🔴 传统BlockingCollection - 每个数据只被消费一次
BlockingCollection<Data> queue = new BlockingCollection<Data>();

// 消费者1
Task.Run(() =>
{
    foreach (var data in queue.GetConsumingEnumerable())
    {
        Console.WriteLine($"消费者1处理: {data}");
        // 这个data从队列中移除,其他消费者拿不到!
    }
});

// 消费者2(如果启动,会拿到不同的数据)
Task.Run(() =>
{
    foreach (var data in queue.GetConsumingEnumerable())
    {
        Console.WriteLine($"消费者2处理: {data}");
    }
});

执行结果

生产者放入: Data1
生产者放入: Data2
消费者1处理: Data1  ← 消费者1拿到Data1
消费者2处理: Data2  ← 消费者2拿到Data2(不是Data1!)

情况2:多个消费者共享同一份数据

// 🟢 使用广播或复制的方式
public class SharedDataQueue
{
    private BlockingCollection<Data> queue = new BlockingCollection<Data>();
    private List<Action<Data>> consumers = new List<Action<Data>>();
    
    public void Start()
    {
        Task.Run(() =>
        {
            foreach (var data in queue.GetConsumingEnumerable())
            {
                // 📢 广播给所有消费者
                foreach (var consumer in consumers)
                {
                    consumer(data);  // 每个消费者都收到相同的数据
                }
            }
        });
    }
    
    public void AddConsumer(Action<Data> consumer)
    {
        consumers.Add(consumer);
    }
    
    public void Enqueue(Data data)
    {
        queue.Add(data);
    }
}

// 使用
var sharedQueue = new SharedDataQueue();
sharedQueue.AddConsumer(data => Console.WriteLine($"消费者1: {data}"));
sharedQueue.AddConsumer(data => Console.WriteLine($"消费者2: {data}"));
sharedQueue.AddConsumer(data => Console.WriteLine($"消费者3: {data}"));

sharedQueue.Start();
sharedQueue.Enqueue(new Data());

情况3:每个消费者都有自己的队列

// 🟡 多队列复制模式
public class MultiConsumerQueue
{
    private List<BlockingCollection<Data>> consumerQueues = new List<BlockingCollection<Data>>();
    
    public void AddData(Data data)
    {
        lock (consumerQueues)
        {
            // 复制到每个消费者的队列
            foreach (var queue in consumerQueues)
            {
                queue.Add(data.Clone());  // 重要:需要深拷贝!
            }
        }
    }
    
    public BlockingCollection<Data> RegisterConsumer()
    {
        var queue = new BlockingCollection<Data>();
        consumerQueues.Add(queue);
        return queue;
    }
}

// 使用
var multiQueue = new MultiConsumerQueue();

// 消费者1获取自己的队列
var queue1 = multiQueue.RegisterConsumer();
Task.Run(() =>
{
    foreach (var data in queue1.GetConsumingEnumerable())
    {
        Console.WriteLine($"消费者1: {data}");
    }
});

// 消费者2获取自己的队列
var queue2 = multiQueue.RegisterConsumer();
Task.Run(() =>
{
    foreach (var data in queue2.GetConsumingEnumerable())
    {
        Console.WriteLine($"消费者2: {data}");
    }
});

// 生产者放入数据(会复制到两个队列)
multiQueue.AddData(new Data());

2. 上位机实际应用场景

场景A:数据处理流水线(数据只能被一个消费者使用)

// ✅ 适合:数据需要顺序处理不同阶段
BlockingCollection<RawData> rawQueue = new BlockingCollection<RawData>();
BlockingCollection<ProcessedData> processedQueue = new BlockingCollection<ProcessedData>();

// 阶段1:数据清洗(唯一消费者)
Task.Run(() =>
{
    foreach (var raw in rawQueue.GetConsumingEnumerable())
    {
        var processed = CleanData(raw);        // 只能被清洗一次
        processedQueue.Add(processed);         // 传递到下一阶段
    }
});

// 阶段2:数据分析(唯一消费者)
Task.Run(() =>
{
    foreach (var processed in processedQueue.GetConsumingEnumerable())
    {
        AnalyzeData(processed);  // 只能被分析一次
    }
});

// 🔴 不能再添加其他消费者同时处理rawQueue或processedQueue!

场景B:数据广播系统(多个消费者使用相同数据)

// ✅ 适合:监控数据需要多界面显示
public class SensorDataBroadcaster
{
    private BlockingCollection<SensorData> sourceQueue = new BlockingCollection<SensorData>();
    private event Action<SensorData> DataArrived;
    
    public SensorDataBroadcaster()
    {
        // 启动分发器
        Task.Run(() =>
        {
            foreach (var data in sourceQueue.GetConsumingEnumerable())
            {
                // 🎯 关键:广播给所有订阅者
                DataArrived?.Invoke(data);
            }
        });
    }
    
    public void Subscribe(Action<SensorData> handler)
    {
        DataArrived += handler;
    }
    
    public void Enqueue(SensorData data)
    {
        sourceQueue.Add(data);
    }
}

// 使用
var broadcaster = new SensorDataBroadcaster();

// 多个界面订阅相同数据
broadcaster.Subscribe(data => UpdateMainDisplay(data));      // 主界面
broadcaster.Subscribe(data => UpdateChart(data));            // 曲线图
broadcaster.Subscribe(data => SaveToDatabase(data));         // 数据库
broadcaster.Subscribe(data => CheckAlarm(data));             // 报警系统

// 传感器数据到达,所有订阅者都能收到
broadcaster.Enqueue(sensorData);

3. 解决方案对比表

方案 数据是否共享 优点 缺点 适用场景
传统单队列 ❌ 不共享 简单高效,节省内存 数据只能处理一次 流水线处理,任务分发
广播模式 ✅ 共享 所有消费者获得相同数据 消费者可能阻塞广播 监控数据多界面显示
多队列复制 ✅ 共享 消费者独立,互不干扰 内存消耗大,需要深拷贝 需要独立处理的场景
发布-订阅 ✅ 共享 高度解耦,灵活扩展 复杂度较高 复杂事件处理系统

4. 代码示例:如何实现数据共享

方法1:使用.NET的Dataflow库(推荐)

using System.Threading.Tasks.Dataflow;

// 🟢 BroadcastBlock:数据广播给所有链接的目标
var broadcastBlock = new BroadcastBlock<SensorData>(data => data);

// 多个消费者链接到同一个生产者
var displayAction = new ActionBlock<SensorData>(data =>
{
    Console.WriteLine($"显示: {data}");
});

var saveAction = new ActionBlock<SensorData>(data =>
{
    Console.WriteLine($"保存: {data}");
});

var alarmAction = new ActionBlock<SensorData>(data =>
{
    Console.WriteLine($"报警检查: {data}");
});

// 链接所有消费者
broadcastBlock.LinkTo(displayAction);
broadcastBlock.LinkTo(saveAction);
broadcastBlock.LinkTo(alarmAction);

// 发送数据,所有消费者都能收到
broadcastBlock.Post(new SensorData());

方法2:使用响应式扩展(Rx.NET

using System.Reactive.Subjects;

// 🟢 Subject作为广播器
Subject<SensorData> dataSubject = new Subject<SensorData>();

// 多个订阅者
dataSubject.Subscribe(data => Console.WriteLine($"订阅者1: {data}"));
dataSubject.Subscribe(data => Console.WriteLine($"订阅者2: {data}"));
dataSubject.Subscribe(data => Console.WriteLine($"订阅者3: {data}"));

// 发布数据,所有订阅者都能收到
dataSubject.OnNext(new SensorData());

方法3:手动实现共享队列

public class SharedConsumerQueue<T>
{
    private BlockingCollection<T> sourceQueue;
    private List<BlockingCollection<T>> consumerQueues = new List<BlockingCollection<T>>();
    private Task dispatcherTask;
    
    public SharedConsumerQueue(int capacity = 100)
    {
        sourceQueue = new BlockingCollection<T>(capacity);
        StartDispatcher();
    }
    
    private void StartDispatcher()
    {
        dispatcherTask = Task.Run(() =>
        {
            foreach (var item in sourceQueue.GetConsumingEnumerable())
            {
                lock (consumerQueues)
                {
                    // 复制到所有消费者的队列
                    foreach (var consumerQueue in consumerQueues)
                    {
                        try
                        {
                            // 这里可以添加数据克隆
                            consumerQueue.Add(item);
                        }
                        catch (InvalidOperationException)
                        {
                            // 消费者队列已关闭
                        }
                    }
                }
            }
        });
    }
    
    public BlockingCollection<T> RegisterConsumer()
    {
        var queue = new BlockingCollection<T>(10);
        lock (consumerQueues)
        {
            consumerQueues.Add(queue);
        }
        return queue;
    }
    
    public void UnregisterConsumer(BlockingCollection<T> queue)
    {
        lock (consumerQueues)
        {
            consumerQueues.Remove(queue);
            queue.CompleteAdding();
        }
    }
    
    public void Enqueue(T item)
    {
        sourceQueue.Add(item);
    }
    
    public void Complete()
    {
        sourceQueue.CompleteAdding();
        dispatcherTask.Wait();
    }
}

5. 上位机实际选择建议

什么时候用单消费者模式

// 场景:数据处理流水线,每个阶段处理不同的任务
if (场景符合以下条件)
{
    // ✅ 数据需要被"消耗"(处理后就没了)
    // ✅ 消费者之间有依赖关系(A处理完B才能处理)
    // ✅ 数据量很大,需要节省内存
    // ✅ 处理顺序很重要
    
    // 例如:
    // 1. 图像处理:采集→去噪→识别→显示
    // 2. 数据解析:原始数据→协议解析→业务处理
    // 3. 任务分发:多个worker处理不同任务
}

什么时候用多消费者共享模式

// 场景:监控数据需要多界面同时显示
if (场景符合以下条件)
{
    // ✅ 多个模块需要相同的数据
    // ✅ 数据可以同时被处理(无冲突)
    // ✅ 实时性要求高(同时更新)
    // ✅ 数据量不大,可以复制
    
    // 例如:
    // 1. 传感器数据:主界面、曲线图、报表同时显示
    // 2. 系统状态:多个监控面板同时更新
    // 3. 报警信息:界面显示、声音报警、短信发送
}

6. 常见问题解答

Q1:如果我想让多个消费者都能处理同一份数据,但希望它们独立处理不相互影响,怎么办?

// 答案:使用数据复制
public class IndependentConsumers
{
    private BlockingCollection<Data> sourceQueue = new BlockingCollection<Data>();
    private List<Func<Data, Data>> processors = new List<Func<Data, Data>>();
    
    public void AddProcessor(Func<Data, Data> processor)
    {
        processors.Add(processor);
        
        // 为每个处理器创建独立的消费者
        Task.Run(() =>
        {
            // 🔑 关键:每个消费者有自己的队列
            var personalQueue = new BlockingCollection<Data>();
            
            // 从源队列获取并复制数据
            Task.Run(() =>
            {
                foreach (var data in sourceQueue.GetConsumingEnumerable())
                {
                    personalQueue.Add(data.Clone());  // 重要:深拷贝!
                }
            });
            
            // 独立处理
            foreach (var data in personalQueue.GetConsumingEnumerable())
            {
                var result = processor(data);
                // 处理结果...
            }
        });
    }
}

Q2:如何保证数据处理的顺序性?

// 方案1:单消费者保证顺序
BlockingCollection<Data> queue = new BlockingCollection<Data>();
// 只有一个消费者,自然保证顺序

// 方案2:多消费者但需要顺序
public class OrderedMultiConsumer
{
    private BlockingCollection<Data> queue = new BlockingCollection<Data>();
    private int consumerCount = 0;
    private object lockObject = new object();
    
    public void ProcessData()
    {
        int myConsumerId = Interlocked.Increment(ref consumerCount) - 1;
        
        foreach (var data in queue.GetConsumingEnumerable())
        {
            // 根据数据ID或时间戳决定由哪个消费者处理
            if (ShouldHandle(data, myConsumerId))
            {
                // 处理数据...
            }
            else
            {
                // 放回队列或其他处理
                queue.Add(data);
                Thread.Sleep(10);
            }
        }
    }
}

7. 总结

需求 推荐方案 关键代码
数据只处理一次 传统单队列 queue.GetConsumingEnumerable()
多个消费者共享数据 广播模式 broadcastBlock.LinkTo(多个ActionBlock)
消费者独立互不影响 多队列复制 为每个消费者创建独立队列 + 数据克隆
复杂事件处理 Rx.NET Subject.Subscribe(多个处理器)
简单监控数据 事件委托 event Action<Data> DataArrived

核心原则

  • 如果消费者会修改数据状态 → 用单消费者或深度拷贝

  • 如果只是读取/显示数据 → 可以多消费者共享

  • 考虑内存和性能平衡

  • 数据一致性很重要时,要谨慎设计

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐