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绑定机制简化队列管理,提供完整的生产消费示例,适用于多线程数据交换场景,具备高性能和易用性特点。

相关推荐
程序员黑豆6 小时前
Java类型推断完全指南:从var到菱形运算符,掌握使用限制与最佳实践
java·前端·ai编程
风流 少年7 小时前
Spring AI 2.0:Memory
java·后端·spring
码匠许师傅7 小时前
【C++ 面试真题】聊聊 C++ 的移动语义与右值引用
java·c++·面试
小席是个热心肠8 小时前
AI相关的自我学习
java·人工智能·学习
波波0078 小时前
C# 15 重磅新特性: 带标签 break 与 continue:重新定义嵌套循环控制流
服务器·前端·c#
数智化管理手记10 小时前
海量数据如何沉淀有效数据资产?数据标准化治理方案如何搭建?
java·大数据·人工智能
cfm_291411 小时前
SpringAI + Ollama 本地大模型
java·开发语言·人工智能·语言模型
wujian831112 小时前
怎么用千问生成word文档:从「格式崩」到「一键过」,AI导出鸭打通最后半厘米
人工智能·ai·c#·word·豆包·deepseek·ai导出鸭
鲸采云SRM采购管理系统12 小时前
采购管理系统哪家好?2026最新测评对比
java·服务器·数据库
0x5313 小时前
Java网络编程(二)
java·服务器·网络