如何避免服务集合DI中SocketHostedService与SocketApi的循环依赖
问题描述
这是基于Marfusios/websocket-client库的WebSocket封装。SocketHostedService是基础WebSocket客户端抽象,包含即发即弃的SendAsync和带超时的请求响应式RequestAsync。当前SocketApi与SocketHostedService形成循环依赖,需求是将SocketHostedService作为基础客户端抽象,SocketApi封装具体实现,同时把RequestAsync的处理函数(如Func<T, ValueTask> onSuccess等)抽象为LogonOperation、LogoffOperation这类操作类,请问如何解决该循环依赖问题?
现有代码示例
DI注册代码
builder.Services.AddSingleton<CqgApi>(); builder.Services.AddSingleton<LogonOperation>(); builder.Services.AddSingleton<CqgHostService>(); builder.Services.AddHostedService(sp => sp.GetRequiredService<CqgHostService>());
SocketHostedService实现
public sealed class SocketHostedService : IHostedService, IAsyncDisposable { private readonly LogonOperation _logonOperation; private readonly CqgWebSocketClient _client; private readonly SocketApi _api; public SocketHostedService(IServiceProvider serviceProvider) { _client = new CqgWebSocketClient(new(settings.Value.Cqg.HostName), settings.Value.Cqg.Timeout, DisconnectActionAsync, ReconnectActionAsync); _api = new SocketApi(this); _logonOperation = ActivatorUtilities.CreateInstance<LogonOperation>( serviceProvider, _api); } public async Task StartAsync(CancellationToken cancellationToken) ... public async Task StopAsync(CancellationToken cancellationToken) ... public async Task SendAsync<T>(IMessage<T> clientMessage) where T : IMessage<ClientMsg>, IMessage<T> ... public async Task RequestAsync( ClientMsg request, Func<ServerMsg, ValueTask> onSuccess, Func<string, ValueTask> onError, Func<ServerMsg, bool> matcher) ... private async Task ProcessServerResponse(ResponseMessage responseMessage) ... private Task DisconnectActionAsync(DisconnectionInfo info) ... private async Task ReconnectActionAsync(ReconnectionInfo info) { await AuthenticateAsync(serverMsg => _logonOperation .HandleResponse(serverMsg.LogonResult), onError => { _logger.LogInformation("OnError = {OnError}", onError); return ValueTask.CompletedTask; }); } }
SocketApi实现
public class SocketApi { private readonly SocketHostedService _client; public SocketApi(SocketHostedService client) => _client = client; public async Task LogonAsync( string username, string password, Func<ServerMsg, ValueTask> onSuccess, Func<string, ValueTask> onError) { ... await _client.RequestAsync(..., onSuccess, onError, ...); } public async Task LogoffAsync( Func<ServerMsg, ValueTask> onSuccess, Func<string, ValueTask> onError) { ... await _client.RequestAsync(..., onSuccess, onError, ...); } public async Task GetAccountInformationAsync( Func<ServerMsg, ValueTask> onSuccess, Func<string, ValueTask> onError) { ... await _client.RequestAsync(..., onSuccess, onError, ...); } }
LogonOperation实现
public class LogonOperation { private readonly ILogger<LogonOperation> _logger; private readonly SocketApi _api; public LogonOperation(ILogger<LogonOperation> logger, SocketApi api) { _logger = logger; _api = api; } public async ValueTask HandleResponse(LogonResult logonResult) { var resultCodeName = Enum.GetName( typeof(LogonResult.Types.ResultCode), (LogonResult.Types.ResultCode)logonResult.ResultCode) ?? "Unspecified"; switch (logonResult.ResultCode) { ... } } }
解决方案
循环依赖的核心原因是SocketHostedService直接实例化SocketApi并传入自身,同时SocketApi依赖具体的SocketHostedService,再加上操作类依赖SocketApi形成闭环。解决思路是通过抽象接口解耦+延迟初始化打破循环。
步骤1:定义基础客户端抽象接口
给SocketHostedService提取接口,让SocketApi依赖接口而非具体实现:
public interface ISocketClient { Task SendAsync<T>(IMessage<T> clientMessage) where T : IMessage<ClientMsg>, IMessage<T>; Task RequestAsync(ClientMsg request, Func<ServerMsg, ValueTask> onSuccess, Func<string, ValueTask> onError, Func<ServerMsg, bool> matcher); }
让SocketHostedService实现该接口:
public sealed class SocketHostedService : IHostedService, IAsyncDisposable, ISocketClient { // 保留原有成员,实现ISocketClient的SendAsync和RequestAsync方法(复用原有逻辑) }
步骤2:调整SocketApi的依赖为接口
修改SocketApi,依赖抽象接口而非具体类:
public class SocketApi { private readonly ISocketClient _client; public SocketApi(ISocketClient client) => _client = client; // 原有LogonAsync、LogoffAsync等方法逻辑保持不变 }
步骤3:重构SocketHostedService的初始化逻辑
移除构造函数中直接实例化SocketApi和LogonOperation的代码,改为在StartAsync中从DI容器延迟获取:
public sealed class SocketHostedService : IHostedService, IAsyncDisposable, ISocketClient { private readonly IServiceProvider _serviceProvider; private readonly CqgWebSocketClient _client; private SocketApi _api; private LogonOperation _logonOperation; public SocketHostedService(IServiceProvider serviceProvider, IOptions<Settings> settings) { _serviceProvider = serviceProvider; _client = new CqgWebSocketClient(new(settings.Value.Cqg.HostName), settings.Value.Cqg.Timeout, DisconnectActionAsync, ReconnectActionAsync); } public async Task StartAsync(CancellationToken cancellationToken) { // 延迟获取实例,此时DI容器已完成注册 _api = _serviceProvider.GetRequiredService<SocketApi>(); _logonOperation = _serviceProvider.GetRequiredService<LogonOperation>(); // 原有启动逻辑 await _client.StartAsync(cancellationToken); } // 原有其他方法保持不变 }
步骤4:更新DI注册配置
调整注册顺序,确保接口与实现绑定,让DI容器可以正常解析所有依赖:
// 注册基础客户端接口与实现 builder.Services.AddSingleton<ISocketClient, SocketHostedService>(); // 注册SocketApi(依赖ISocketClient) builder.Services.AddSingleton<SocketApi>(); // 注册操作类 builder.Services.AddSingleton<LogonOperation>(); // 注册HostedService builder.Services.AddHostedService(sp => sp.GetRequiredService<SocketHostedService>());
额外优化建议
- 尽量避免在构造函数中执行复杂初始化,遵循HostedService生命周期规范,将依赖获取和初始化逻辑放到
StartAsync中。 - 如果想进一步解耦,可以引入工厂模式(比如
ISocketApiFactory),封装SocketApi的创建逻辑,避免直接使用IServiceProvider获取实例。
内容的提问来源于stack exchange,提问作者nop
相关产品推荐
相关产品推荐

