C#上位机生产者消费者模式
目录
使用Channel实现(.NET Core 3.0+ 推荐)
Q1:如果我想让多个消费者都能处理同一份数据,但希望它们独立处理不相互影响,怎么办?
C上位机生产者消费者模式
生产者-消费者模式是一种经典的设计模式,它将数据的生成(生产者)和处理(消费者)分离到不同的模块或线程中。这种模式的核心在于一个共享的缓冲区,生产者将数据放入缓冲区,而消费者从缓冲区中取出数据进行处理。这种模式有助于提高系统的响应性和吞吐量,因为它允许生产者和消费者并行工作,互不干扰。
三大组件:
-
生产者(Producer):产生数据
-
缓冲区(Buffer):存放数据(队列)
-
消费者(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);
}
}
生产者消费者模式要点总结 📝
三大组件:
-
生产者(Producer):产生数据
-
缓冲区(Buffer):存放数据(队列)
-
消费者(Consumer):处理数据
四种实现方式对比:
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| BlockingCollection | 简单易用,线程安全 | .NET Framework 4+ | 大多数场景 |
| Channel | 性能好,API现代化 | .NET Core 3.0+ | 新项目推荐 |
| 手动锁+队列 | 完全控制 | 代码复杂 | 特殊需求 |
| TPL Dataflow | 功能强大 | 学习成本高 | 复杂数据流 |
核心优势:
-
解耦:生产者和消费者独立工作
-
缓冲:平滑处理速度差异
-
并发:充分利用多核CPU
-
可扩展:容易增加生产者或消费者
常见问题及解决方案:
// 问题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:数据只能被一个消费者使用(最常见)
// 🔴 传统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 |
核心原则:
-
如果消费者会修改数据状态 → 用单消费者或深度拷贝
-
如果只是读取/显示数据 → 可以多消费者共享
-
考虑内存和性能平衡
-
数据一致性很重要时,要谨慎设计
更多推荐


所有评论(0)