You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# 不使用DI框架如何创建Azure ServiceBusSender单例实例

无DI/IoC场景下ServiceBusSender单例实现方案

Azure Service Bus官方SDK中的ServiceBusClient、ServiceBusSender均为线程安全类型,设计层面就推荐作为单例长期复用,避免频繁创建销毁带来的连接开销。以下是纯原生实现的线程安全单例方案,无需依赖任何DI/IoC框架:

using Azure.Messaging.ServiceBus;
using System.Collections.Concurrent;

public sealed class ServiceBusQueueSender : IDisposable
{
    // 同个Service Bus命名空间全局复用一个Client,减少AMQP连接开销
    private static readonly ServiceBusClient _serviceBusClient;
    private readonly ServiceBusSender _sender;
    private bool _disposed = false;

    // 静态构造函数仅执行一次,完成全局Client初始化
    static ServiceBusQueueSender()
    {
        // 连接字符串从环境变量读取,禁止硬编码到业务代码
        var connectionString = Environment.GetEnvironmentVariable("AzureServiceBus_ConnectionString");
        if (string.IsNullOrWhiteSpace(connectionString))
        {
            throw new InvalidOperationException("未配置Azure Service Bus连接字符串");
        }
        // 如需自定义重试、超时、代理等配置,可在此处传入ServiceBusClientOptions实例
        _serviceBusClient = new ServiceBusClient(connectionString);
    }

    // 私有构造函数,禁止外部直接实例化
    private ServiceBusQueueSender(string queueOrTopicName)
    {
        _sender = _serviceBusClient.CreateSender(queueOrTopicName);
    }

    // 按队列/主题名称缓存Sender实例,线程安全
    private static readonly ConcurrentDictionary<string, ServiceBusQueueSender> _senderCache = new();

    /// <summary>
    /// 获取指定队列/主题的Sender单例
    /// </summary>
    public static ServiceBusQueueSender GetSender(string queueOrTopicName)
    {
        if (string.IsNullOrWhiteSpace(queueOrTopicName))
        {
            throw new ArgumentException("队列/主题名不能为空", nameof(queueOrTopicName));
        }
        return _senderCache.GetOrAdd(queueOrTopicName, name => new ServiceBusQueueSender(name));
    }

    /// <summary>
    /// 发送单条消息
    /// </summary>
    public async Task SendMessageAsync(ServiceBusMessage message, CancellationToken cancellationToken = default)
    {
        ObjectDisposedException.ThrowIf(_disposed, this);
        await _sender.SendMessageAsync(message, cancellationToken).ConfigureAwait(false);
    }

    /// <summary>
    /// 批量发送消息
    /// </summary>
    public async Task SendBatchAsync(IReadOnlyList<ServiceBusMessage> messages, CancellationToken cancellationToken = default)
    {
        ObjectDisposedException.ThrowIf(_disposed, this);
        await _sender.SendMessagesAsync(messages, cancellationToken).ConfigureAwait(false);
    }

    public void Dispose()
    {
        if (_disposed) return;
        _sender.Dispose();
        _disposed = true;
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// 应用退出时调用,释放全局Client连接资源
    /// </summary>
    public static async Task ShutdownAsync()
    {
        foreach (var sender in _senderCache.Values)
        {
            sender.Dispose();
        }
        _senderCache.Clear();
        await _serviceBusClient.DisposeAsync().ConfigureAwait(false);
    }
}

使用示例

// 获取指定队列的Sender实例,重复传入同个队列名会返回同一个实例
var orderSender = ServiceBusQueueSender.GetSender("order-processing-queue");

// 构造消息发送
var msg = new ServiceBusMessage(BinaryData.FromString("{\"orderId\":123, \"status\":\"paid\"}"))
{
    MessageId = Guid.NewGuid().ToString(),
    ContentType = "application/json"
};
await orderSender.SendMessageAsync(msg);

// 应用退出前执行资源释放,控制台程序可在主逻辑结束前调用,桌面程序可绑定应用退出事件
// await ServiceBusQueueSender.ShutdownAsync();

关键说明

  • 静态构造函数保证ServiceBusClient全局只初始化一次,ConcurrentDictionary保证多线程场景下每个队列/主题的Sender只会创建一个实例,完全满足单例要求
  • 所有实例均为线程安全,可在多线程、异步场景下直接调用,无需额外加锁
  • 禁止每次发送消息都重新创建Client或Sender,否则会导致TCP连接数暴涨、发送延迟升高,甚至触发Service Bus的连接限流
  • 生产环境不要硬编码连接字符串,建议从环境变量、配置中心或者密钥管理服务读取
  • 自定义重试策略、超时时间、传输模式等全局配置,在初始化ServiceBusClient时传入ServiceBusClientOptions即可,所有Sender会自动继承该配置
  • 该实现同时兼容向Service Bus主题(Topic)发送消息的场景,只需要传入主题名调用GetSender方法即可

内容的提问来源于stack exchange,提问作者Aryan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 01:51:43