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

如何避免服务集合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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 09:08:10