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

如何处理Discord与DirectLine API客户端的双向依赖?

嘿,你碰到的双向依赖问题确实是个典型的设计陷阱——不仅会让DI容器初始化卡壳,还会让代码耦合得死死的,后续维护简直是噩梦。咱们先拆解下当前代码里的核心问题,再一步步给出优化方案。

当前代码的主要问题

  1. 双向依赖死循环:DiscordClient依赖IDirectLineClient,DirectLineClient又依赖IDiscordClient,DI容器根本没法正常解析这两个服务的实例。
  2. 静态字段滥用:_directLineClient、_secret这些静态字段完全没必要,单例服务用实例字段就够了,静态字段会带来并发安全问题,还会让测试变得困难。
  3. 异步初始化逻辑不规范:把初始化逻辑放在静态的InitializeAsync里,还在里面用Task.Delay(-1)阻塞线程,这完全不符合.NET依赖注入的生命周期管理规范。

优化方案:用事件中介者模式解耦

最干净的解决办法是引入事件中介者(Event Mediator),让两个客户端不再直接依赖彼此,而是通过发布/订阅事件来通信。这样既消除了双向依赖,还能让每个客户端的职责更单一。

第一步:定义事件和中介者接口

先创建事件类来传递消息,再写一个简单的中介者实现(生产环境可以直接用成熟的库比如MediatR):

// 事件定义:承载消息数据
public record DiscordMessageReceivedEvent(string MessageContent, SocketMessage OriginalMessage);
public record DirectLineResponseReceivedEvent(string ResponseContent, SocketMessage TargetMessage);

// 中介者接口:定义发布和订阅行为
public interface IEventMediator
{
    Task PublishAsync<TEvent>(TEvent @event) where TEvent : class;
    void Subscribe<TEvent>(Func<TEvent, Task> handler) where TEvent : class;
}

// 内存版中介者实现(足够满足你的场景)
public class InMemoryEventMediator : IEventMediator
{
    private readonly Dictionary<Type, List<Delegate>> _handlers = new();

    public Task PublishAsync<TEvent>(TEvent @event) where TEvent : class
    {
        if (_handlers.TryGetValue(typeof(TEvent), out var handlers))
        {
            return Task.WhenAll(handlers.Cast<Func<TEvent, Task>>().Select(h => h(@event)));
        }
        return Task.CompletedTask;
    }

    public void Subscribe<TEvent>(Func<TEvent, Task> handler) where TEvent : class
    {
        var eventType = typeof(TEvent);
        if (!_handlers.ContainsKey(eventType))
        {
            _handlers[eventType] = new List<Delegate>();
        }
        _handlers[eventType].Add(handler);
    }
}

第二步:重构DiscordClient

移除对IDirectLineClient的直接依赖,改为订阅/发布事件:

namespace Microsoft.Chive
{
    public interface IDiscordClient
    {
        Task InitializeAsync();
    }

    public class DiscordClient : IDiscordClient
    {
        private readonly DiscordSocketClient _client;
        private readonly CommandService _commands;
        private readonly string _botToken;
        private readonly IEventMediator _eventMediator;
        private readonly IServiceProvider _provider;

        // 构造函数只做依赖注入,不做初始化逻辑
        public DiscordClient(IConfiguration configuration, IEventMediator eventMediator, IServiceProvider provider)
        {
            _botToken = configuration.GetRequiredSection("discordClientSecret").Value;
            _eventMediator = eventMediator;
            _provider = provider;
            _client = new DiscordSocketClient();
            _commands = new CommandService();
        }

        public async Task InitializeAsync()
        {
            // 监听Discord消息,收到后发布事件
            _client.MessageReceived += OnMessageReceived;
            // 订阅DirectLine的响应事件,处理后发送到Discord
            _eventMediator.Subscribe<DirectLineResponseReceivedEvent>(OnDirectLineResponse);
            
            await _client.LoginAsync(TokenType.Bot, _botToken);
            await _client.StartAsync();
        }

        private async Task OnMessageReceived(SocketMessage socketMessage)
        {
            if (socketMessage is SocketUserMessage userMessage)
            {
                await _eventMediator.PublishAsync(new DiscordMessageReceivedEvent(userMessage.Content, userMessage));
            }
        }

        private async Task OnDirectLineResponse(DirectLineResponseReceivedEvent @event)
        {
            var context = new CommandContext(_client, @event.TargetMessage);
            await _commands.ExecuteAsync(context, 0, _provider);
        }
    }
}

第三步:重构DirectLineClient

同样移除对IDiscordClient的依赖,改为通过事件通信:

public interface IDirectLineClient
{
    Task InitializeAsync();
    Task StartConversationAsync();
}

public class DirectLineClient : IDirectLineClient
{
    private readonly Microsoft.Bot.Connector.DirectLine.DirectLineClient _directLineClient;
    private readonly string _handle;
    private readonly IEventMediator _eventMediator;
    private Conversation _conversation;

    public DirectLineClient(IConfiguration configuration, IEventMediator eventMediator)
    {
        var secret = configuration.GetRequiredSection("directLineSecret").Value;
        _handle = configuration.GetRequiredSection("directLineHandle").Value;
        _eventMediator = eventMediator;
        _directLineClient = new Microsoft.Bot.Connector.DirectLine.DirectLineClient(secret);
    }

    public async Task InitializeAsync()
    {
        // 订阅Discord的消息事件,收到后发送到DirectLine API
        _eventMediator.Subscribe<DiscordMessageReceivedEvent>(OnDiscordMessageReceived);
        // 启动DirectLine响应监听(这里用轮询示例,你也可以换成WebSocket)
        _ = ListenForResponses();
    }

    public async Task StartConversationAsync()
    {
        _conversation = await _directLineClient.Conversations.StartConversationAsync();
    }

    private async Task OnDiscordMessageReceived(DiscordMessageReceivedEvent @event)
    {
        if (_conversation == null)
        {
            await StartConversationAsync();
        }

        var activity = new Activity
        {
            From = new ChannelAccount("user"),
            Text = @event.MessageContent,
            Type = ActivityTypes.Message
        };

        await _directLineClient.Conversations.PostActivityAsync(_conversation.ConversationId, activity);
    }

    // 监听DirectLine的响应,收到后发布事件给Discord
    private async Task ListenForResponses()
    {
        while (true)
        {
            if (_conversation == null)
            {
                await Task.Delay(1000);
                continue;
            }

            var activities = await _directLineClient.Conversations.GetActivitiesAsync(_conversation.ConversationId, _conversation.Watermark);
            _conversation.Watermark = activities.Watermark;

            foreach (var activity in activities.Activities.Where(a => a.Type == ActivityTypes.Message && a.From.Id != "user"))
            {
                // 这里需要关联到对应的Discord消息,你可以维护一个ConversationId和SocketMessage的映射表
                // 示例中暂时用占位符,实际要替换成真实的TargetMessage
                await _eventMediator.PublishAsync(new DirectLineResponseReceivedEvent(activity.Text, /* 对应的SocketMessage实例 */));
            }

            await Task.Delay(1000);
        }
    }
}

第四步:更新DI注册与生命周期管理

用IHostedService来处理异步初始化,避免阻塞DI容器:

public void ConfigureServices(IServiceCollection services)
{
    ...
    // 注册事件中介者
    services.AddSingleton<IEventMediator, InMemoryEventMediator>();
    // 注册两个客户端,现在它们不再互相依赖了
    services.AddSingleton<IDiscordClient, DiscordClient>();
    services.AddSingleton<IDirectLineClient, DirectLineClient>();
    // 添加HostedService来管理客户端的初始化和销毁
    services.AddHostedService<ClientInitializationHostedService>();
    ...
}

// 自定义HostedService处理异步初始化逻辑
public class ClientInitializationHostedService : IHostedService
{
    private readonly IDiscordClient _discordClient;
    private readonly IDirectLineClient _directLineClient;

    public ClientInitializationHostedService(IDiscordClient discordClient, IDirectLineClient directLineClient)
    {
        _discordClient = discordClient;
        _directLineClient = directLineClient;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        // 并行初始化两个客户端,提高启动速度
        await Task.WhenAll(
            _discordClient.InitializeAsync(),
            _directLineClient.InitializeAsync()
        );
        // 启动DirectLine对话
        await _directLineClient.StartConversationAsync();
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        // 清理资源:登出Discord客户端
        if (_discordClient is DiscordClient discordClient)
        {
            // 可以给DiscordClient加一个Stop方法,暴露内部的_client操作
            await discordClient._client.StopAsync();
            await discordClient._client.LogoutAsync();
        }
    }
}

优化后的好处

  1. 彻底消除双向依赖:两个客户端现在只依赖事件中介者,完全解耦,DI容器可以正常解析。
  2. 职责更单一:DiscordClient只处理Discord平台的消息收发,DirectLineClient只处理DirectLine API的逻辑,代码更清晰。
  3. 可扩展性更强:以后要加新的消息渠道(比如Slack),只需要新增一个客户端并订阅/发布对应的事件就行,不用修改现有代码。
  4. 符合.NET最佳实践:用IHostedService管理异步生命周期,移除了静态字段,线程更安全,测试也更容易。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:32:14