如何处理Discord与DirectLine API客户端的双向依赖?
嘿,你碰到的双向依赖问题确实是个典型的设计陷阱——不仅会让DI容器初始化卡壳,还会让代码耦合得死死的,后续维护简直是噩梦。咱们先拆解下当前代码里的核心问题,再一步步给出优化方案。
当前代码的主要问题
- 双向依赖死循环:
DiscordClient依赖IDirectLineClient,DirectLineClient又依赖IDiscordClient,DI容器根本没法正常解析这两个服务的实例。 - 静态字段滥用:
_directLineClient、_secret这些静态字段完全没必要,单例服务用实例字段就够了,静态字段会带来并发安全问题,还会让测试变得困难。 - 异步初始化逻辑不规范:把初始化逻辑放在静态的
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(); } } }
优化后的好处
- 彻底消除双向依赖:两个客户端现在只依赖事件中介者,完全解耦,DI容器可以正常解析。
- 职责更单一:DiscordClient只处理Discord平台的消息收发,DirectLineClient只处理DirectLine API的逻辑,代码更清晰。
- 可扩展性更强:以后要加新的消息渠道(比如Slack),只需要新增一个客户端并订阅/发布对应的事件就行,不用修改现有代码。
- 符合.NET最佳实践:用
IHostedService管理异步生命周期,移除了静态字段,线程更安全,测试也更容易。
内容的提问来源于stack exchange,提问作者kemakino
相关产品推荐
相关产品推荐

