C#高级:使用ConcurrentQueue做一个简易进程内通信的消息队列

文章目录

一、简介

使用ConcurrentQueue实现线程安全的进程内消息队列,支持多生产者/消费者模式。

二、使用场景

多线程间数据交换、异步任务处理、日志缓冲等需要线程安全队列的场景。

三、好处

  • 线程安全:内置锁机制,无需额外同步
  • 高性能:无锁设计减少竞争
  • 易用性强:实例绑定Key,简化队列管理

四、代码

csharp 复制代码
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// 简易队列管理器(实例绑定唯一Key,无需重复传参)
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
public class EasyQueue<T> : IDisposable
{
    // 静态字典:全局管理所有Key对应的队列(保证Key唯一)
    private static readonly ConcurrentDictionary<string, ConcurrentQueue<T>> _globalQueueDict = new();

    // 实例专属Key(创建实例时指定,后续操作无需传参)
    public string QueueKey { get; }

    // 实例绑定的队列(避免每次从全局字典获取)
    private ConcurrentQueue<T> _boundQueue;

    /// <summary>
    /// 构造函数:注册Key并绑定专属队列(核心优化点)
    /// </summary>
    /// <param name="queueKey">队列唯一标识(注册Key)</param>
    /// <exception cref="ArgumentNullException">Key为空时抛出</exception>
    public EasyQueue(string queueKey)
    {
        if (string.IsNullOrWhiteSpace(queueKey))
            throw new ArgumentNullException(nameof(queueKey), "队列Key不能为空或空白");

        QueueKey = queueKey;
        // 注册Key:不存在则创建新队列,存在则绑定已有队列(线程安全)
        _boundQueue = _globalQueueDict.GetOrAdd(queueKey, _ => new ConcurrentQueue<T>());
    }

    #region 核心方法(无需传Key,直接操作实例绑定的队列)
    /// <summary>
    /// 单个元素入队(实例方法,无需传Key)
    /// </summary>
    /// <param name="item">要入队的元素</param>
    public void Enqueue(T item)
    {
        _boundQueue.Enqueue(item);
    }

    /// <summary>
    /// 批量元素入队(实例方法,无需传Key)
    /// </summary>
    /// <param name="items">要入队的元素列表</param>
    /// <exception cref="ArgumentNullException">列表为空时抛出</exception>
    public void Enqueue(List<T> items)
    {
        if (items == null || items.Count == 0)
            throw new ArgumentNullException(nameof(items), "入队元素列表不能为空或空列表");

        foreach (var item in items)
        {
            _boundQueue.Enqueue(item);
        }
    }

    /// <summary>
    /// 出队(实例方法,无需传Key)
    /// </summary>
    /// <param name="item">出队的元素(队列为空时返回default(T))</param>
    /// <returns>是否出队成功(true=成功,false=队列为空)</returns>
    public bool Dequeue(out T item)
    {
        return _boundQueue.TryDequeue(out item);
    }

    /// <summary>
    /// 查询队列所有元素(实例方法,无需传Key)
    /// </summary>
    /// <returns>队列元素列表(队列为空返回空List)</returns>
    public List<T> GetQueueItems()
    {
        return _boundQueue.ToList();
    }
    #endregion

    #region 扩展实用方法(实例级,无需传Key)
    /// <summary>
    /// 判断当前实例绑定的队列是否为空
    /// </summary>
    public bool IsEmpty => _boundQueue.IsEmpty;

    /// <summary>
    /// 获取当前队列的元素数量
    /// </summary>
    public int Count => _boundQueue.Count;

    /// <summary>
    /// 清空当前队列
    /// </summary>
    public void Clear()
    {
        while (_boundQueue.TryDequeue(out _)) { }
    }

    /// <summary>
    /// 静态方法:移除全局字典中的指定Key(注销队列)
    /// </summary>
    /// <param name="queueKey">要注销的Key</param>
    /// <returns>是否注销成功</returns>
    public static bool UnregisterQueue(string queueKey)
    {
        if (string.IsNullOrWhiteSpace(queueKey))
            throw new ArgumentNullException(nameof(queueKey));

        return _globalQueueDict.TryRemove(queueKey, out _);
    }

    /// <summary>
    /// 静态方法:查询全局已注册的所有队列Key
    /// </summary>
    /// <returns>所有Key的列表</returns>
    public static List<string> GetAllRegisteredKeys()
    {
        return _globalQueueDict.Keys.ToList();
    }
    #endregion

    #region 资源释放
    private bool _disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                // 释放实例时清空绑定的队列(可选,根据业务需求调整)
                Clear();
            }
            _disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    #endregion
}



class Program
{
    static void Main(string[] args)
    {
        // 定义队列Key
        const string testKey = "MultiThreadQueue";

        // ========== 线程1:生产数据(入队) ==========
        Task produceTask = Task.Run(() =>
        {
            // 创建生产端实例(绑定testKey)
            var producerQueue = new EasyQueue<int>(testKey);
            for (int i = 1; i <= 100; i++)
            {
                producerQueue.Enqueue(i);
                Console.WriteLine($"【生产线程】入队:{i}");
                Thread.Sleep(1000); // 模拟生产耗时
            }
            Console.WriteLine("【生产线程】生产完成!");
        });

        // ========== 线程2:消费数据(出队) ==========
        Task consumeTask = Task.Run(() =>
        {
            // 创建消费端实例(绑定同一个testKey)
            var consumerQueue = new EasyQueue<int>(testKey);
            int consumeCount = 0;
            // 循环消费,直到生产完成且队列为空
            while (!produceTask.IsCompleted || !consumerQueue.IsEmpty)
            {
                if (consumerQueue.Dequeue(out int item))
                {
                    consumeCount++;
                    Console.WriteLine($"【消费线程】出队:{item}");
                }
                else
                {
                    Thread.Sleep(5); // 队列为空时短暂等待,避免空轮询
                }
            }
            Console.WriteLine($"【消费线程】消费完成!共消费{consumeCount}个元素");
        });

        // 等待所有线程完成
        Task.WaitAll(produceTask, consumeTask);
        Console.WriteLine("\n所有操作完成!");
    }
}

总结

通过ConcurrentQueue和ConcurrentDictionary实现了一个线程安全的进程内消息队列。该方案支持Key绑定机制简化队列管理,提供完整的生产消费示例,适用于多线程数据交换场景,具备高性能和易用性特点。

相关推荐
空中海几秒前
Docker入门到精通
java·docker·eureka
生而为虫3 分钟前
在VScode中使用Claude Code agent并配置模型(仅mac电脑实际操作,windows电脑未实际操作如有问题可留言)
windows·vscode·macos
itzixiao12 分钟前
L1-067 洛希极限(10分)[java][python]
java·开发语言·算法
叶小鸡18 分钟前
Java 篇-项目实战-天机学堂(从0到1)-day10
windows·microsoft
java1234_小锋19 分钟前
Spring AI 2.0 开发Java Agent智能体 - Spring AI项目调用本地Ollama模型
java·人工智能·spring·spring ai2.0
二哈赛车手20 分钟前
新人笔记---多策略搭建策略执行链实现RAG检索后过滤
java·笔记·spring·设计模式·ai·策略模式
PESS ABIN21 分钟前
JavaWeb项目打包、部署至Tomcat并启动的全程指南(图文详解)
java·tomcat
AI进化营-智能译站27 分钟前
ROS2 C++开发系列15-模板实现通用算法|宏定义ROS2调试开关|一次编码适配多平台
java·c++·算法·ai
刀法如飞29 分钟前
Java数组去重的20种实现方式——指导AI解决不同问题的思路
java·算法·面试
薪火铺子39 分钟前
SpringMVC请求处理流程源码解析(第1篇):请求入口与处理器映射
java·后端·spring