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

相关推荐
heartbeat..3 小时前
数据库基础知识体系:概念、约束、范式与国产产品
java·数据库·学习笔记·国产数据库
Never_Satisfied3 小时前
C#获取汉字拼音字母方法总结
开发语言·c#
PXM的算法星球3 小时前
【操作系统】哲学家就餐问题实现详解
java
2301_815357703 小时前
Java项目架构从单体架构到微服务架构的发展演变
java·微服务·架构
Ethan-D3 小时前
#每日一题19 回溯 + 全排列思想
java·开发语言·python·算法·leetcode
Echoo华地4 小时前
idea运行程序默认线程为daemon线程的问题
java·ide·intellij-idea
歪楼小能手4 小时前
Android16系统go版关闭重力旋转开关后缺失手动旋转屏幕悬浮按钮
android·java·平板
Coder_Boy_4 小时前
基于SpringAI的在线考试系统-DDD业务领域模块设计思路
java·数据库·人工智能·spring boot·ddd
曹轲恒4 小时前
SSM项目的部署
java·ssm