C# 阻塞队列(BlockingCollection)

文章目录


前言

--

一、什么是队列?什么是阻塞队列?

  • 队列(Queue)就是排队就跟你在超市结账、车站买票一样,先来的先走后来的后走,这种先进先出(FIFO, First In First Out)的数据结构,就叫队列。
  • 阻塞队列(BlockingCollection)简单表述就是需要等待阻塞的队列。队列为空时,取数据线程会自动阻塞休眠,直到有数据才唤醒;队列有上限时,存数据线程会自动阻塞休眠,直到有空位才唤醒,这种会自动等待、不报错、不卡死的队列,就叫阻塞队列。
  • 我们在通信和数据处理中经常用到队列,本文就C#中的队列进行简单描述。C#中的类是叫Queue。阻塞队列在C#中的类叫BlockingCollection。

二、普通和阻塞队列对比

特性 Queue 普通队列 BlockingCollection 阻塞队列
线程安全 非安全(多线程必报错) 完全线程安全
阻塞能力 无(空 / 满直接抛异常) 有(空时取阻塞,满时存阻塞)
并发支持 不支持多线程并发 原生支持多线程并发
边界限制 无上限(无限扩容) 可设置最大容量(有界队列)
完成标记 不支持 支持 CompleteAdding() 标记生产完成
性能 单线程极快 多线程下稳定,有轻微锁开销
适用场景 单线程业务、简单数据缓存 多线程、异步通信、任务队列、生产者消费者
  • 从上表可以寄看出 普通队列的优势无锁、无阻塞逻辑,单线程下速度最快轻量简单内存占用小。比较适用单线程处理数据(如顺序缓存、临时存储)无并发、无异步的简单业务。
  • 而阻塞队列的线程安全方面有着明显的优势,多线程并发读写不会崩溃、不会数据错乱。自动阻塞、唤醒和优雅退出,可防止过快导致内存崩溃等,更加适用于多线程任务调度。

三、代码描述

  • 代码将普通队列和阻塞队列一起描述 可以通过宏定义开关(SELECT_QUEUE)切换,任君自主选择。无论是使用普通队列还是阻塞队列都在一定数据量内都是可以正常运行的。
  • 实例中除了主线程,还启动了2个独立的线程来分别处理接收数据和数据解析。

1、新建队列

  • 新建初始化队列没有书名特殊,new一个即可,要注意using引用相关的库
c 复制代码
//#define SELECT_QUEUE //宏定义开关
using System.Collections;
using System.Collections.Concurrent;
using System.Threading;

 //新建队列
#if SELECT_QUEUE
         //新建一个队列接收数据
         public Queue q_rx = new Queue();
#else
        //新建阻塞队列BlockingCollection
        public BlockingCollection<byte[]> q_rx = new BlockingCollection<byte[]>();
#endif

2、入队列

  • 在线程_receiveThread中获取的数据直接入队列,他们的方法不一样,普通队列为Enqueue()方法,阻塞队列入队则是Add()方法。
c 复制代码
Thread _receiveThread;//接收线程
public bool rx_thread_stop_flag = false;//线程停止标志 
//接收到的数据, 数据长度不确定
public byte[]RxBuf;
/// <summary>
/// 开关接收线程
/// </summary>
private void RxThreadOnOff(bool on_off)
{
    if (on_off)
    {
        _receiveThread = new Thread(() => ReceiveData());//开启一个线程来不断的接收数据
        _receiveThread.IsBackground = true;
        _receiveThread.Name = "UartReceiveDataThread";
        _receiveThread.Start();
        Console.Write($"UartReceiveDataThread.ID:{_receiveThread.ManagedThreadId}\n");

    }
    else
    {
        rx_thread_stop_flag = true;
        while (_receiveThread!=null)//等待停止
        {            
            _receiveThread.Join(300);  // 等待线程结束
            _receiveThread = null;
        }
        rx_thread_stop_flag = false;
    }
}
 
/// <summary>
/// 接收数据处理入队
/// </summary>
public void ReceiveData()
{
	   //接收数据线程循环
		while ( !rx_thread_stop_flag)//线程循环
		{
			//间隔20ms
			Thread.Sleep(20)
			//获取实时的数据
			byte[] data=ReadData();
			//数据长度
			int len = data.Length; 
			RxBuf = new byte[len];
			Array.Copy(data, RxBuf, len);//将接收到的数据复制到全局变量RxBuf中

#if SELECT_QUEUE 
            q_rx.Enqueue(RxBuf);//数据入到普通队列
#else
            q_rx.Add(RxBuf);//数据入到阻塞队列
#endif
       }
}

3、出队列

  • 从下面代码中可以看到,RxParsingThread线程中出队列时,普通队列使用Dequeue()方法出队,而阻塞队列则使用Take()方法等待和出队列。出队后通过BufferReceiveParsing()中自定义解析或后续动作
    代码如下(示例):
c 复制代码
//接收解析线程
Thread RxParsingThread;
/// <summary>
/// 启动解析线程
/// </summary>
private void RxParsingThreadStart()
{
    RxParsingThread = new Thread(new ThreadStart(BufferReceiveParsingLoop))//指定线程函数
    {
        IsBackground = true,//可后台运行
        Name = "RxParsingThread"//线程名
    };
    RxParsingThread.Start();//启动线程
    Console.Write($"RxParsingThreadStart.ID:{RxParsingThread.ManagedThreadId}\n");
}

/// <summary>
/// 接收解析循环
/// </summary>
public void BufferReceiveParsingLoop()
{ 
	while (true)
	{ 
#if SELECT_QUEUE	
	    //判断队列是否有值
	    if (q_rx.Count > 0 )
	    {               
	        //数据出队列
	        byte[] rxData = (byte[])q_rx.Dequeue();	
	        BufferReceiveParsing(rxData);//解析数据                   
	    }
	    else
	    {
	        Thread.Sleep(1);
	    }   
#else  
      //阻塞队列出队
	    if (!q_rx.IsCompleted)
	    {
	        //数据出队列,为空阻塞在此
	        byte[] rxData = q_rx.Take(); // 阻塞等待数据
	        BufferReceiveParsing(rxData);//解析数据
	    }
#endif
	}
			
}

//自定义数据解析
public void BufferReceiveParsing(byte[] rxData)
{}

总结

  • 本文描述了C# 中常用的两种队列,并进行了对比和较为详细的代码描述。
  • 单线程场景,用普通队列Queue,轻量、速度快。
  • 多线程 / 多并发场景,强烈推荐用阻塞队列BlockingCollection,安全、自动阻塞、代码同样简洁。
  • 线程安全 + 阻塞等待,这是阻塞队列最核心的价值。
相关推荐
OctShop大型商城源码6 小时前
.NET线上商城源码_C#商城源码_技术赋能下的电商新生态
开发语言·c#·.net·商城系统源码
hixiong1239 小时前
C#文件目录结构生成工具
开发语言·c#
滴滴答答哒11 小时前
# SqlSugar 差异日志功能实现
c#
顾温12 小时前
协程结束——实测
开发语言·unity·c#
唐青枫14 小时前
C#.NET YARP 详解:用 ASP.NET Core 打造高性能反向代理网关
c#·.net
asdzx6714 小时前
告别手工复制:用 C# 轻松合并多份 Word
c#·word
步步为营DotNet15 小时前
NET 11 中 C# 14 新特性在云原生微服务架构的深度实践
云原生·架构·c#
不会编程的懒洋洋16 小时前
WPF 性能优化+异步+渲染
开发语言·笔记·性能优化·c#·wpf·图形渲染·线程
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ1 天前
通过java后端代码来实现给word内容补充格式文本内容控件,以及 设置控件的标记和标题
java·c#·word