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

.NET 7中如何识别已订阅的GRPC流服务异常下线

解决方案:识别gRPC流中具体下线的服务并通知UI

你的核心问题是在gRPC流断开时无法定位具体是哪个服务(Service1/Service2)下线,当前Polly策略仅捕获通用的Unavailable异常,未关联到具体服务。以下是落地性强的实现方案:

一、重构订阅逻辑:为每个服务单独维护流连接

当前通过单一流接收所有服务通知的方式,无法区分断开的具体服务。需为每个服务建立独立订阅流,让每个流的异常直接关联对应服务。

1. 拆分订阅方法

将原单一Subscribe拆分为各服务专属订阅,独立处理流和异常:

// 定义服务下线通知委托,用于触发UI更新
public delegate void ServiceOfflineHandler(string serviceName);
public event ServiceOfflineHandler? OnServiceOffline;

// Service1专属订阅
private async Task SubscribeToService1()
{
    await Policy
        .Handle<RpcException>(e => e.Status.StatusCode == StatusCode.Unavailable)
        .WaitAndRetryAsync(
            10,
            attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)),
            onRetry: (e, ts) => {
                logger.Warning("Service1 连接丢失,{Seconds}秒后重试", ts.Seconds);
                // 触发服务下线通知
                OnServiceOffline?.Invoke("Service1");
            })
        .ExecuteAsync(async () => {
            // 调用Service1的专属订阅接口
            IAsyncEnumerable<Notification> stream = await subscribe.SubscribeToService1(currentUser);
            await foreach (var ev in stream)
            {
                logger.Warning("收到Service1通知");
                // 处理Service1正常业务逻辑
            }
        });
}

// Service2专属订阅
private async Task SubscribeToService2()
{
    await Policy
        .Handle<RpcException>(e => e.Status.StatusCode == StatusCode.Unavailable)
        .WaitAndRetryAsync(
            10,
            attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)),
            onRetry: (e, ts) => {
                logger.Warning("Service2 连接丢失,{Seconds}秒后重试", ts.Seconds);
                // 触发服务下线通知
                OnServiceOffline?.Invoke("Service2");
            })
        .ExecuteAsync(async () => {
            // 调用Service2的专属订阅接口
            IAsyncEnumerable<Notification> stream = await subscribe.SubscribeToService2(currentUser);
            await foreach (var ev in stream)
            {
                // 处理Service2正常业务逻辑
            }
        });
}

// 启动所有服务订阅
public async Task StartAllSubscriptions()
{
    // 并行启动两个订阅任务
    await Task.WhenAll(SubscribeToService1(), SubscribeToService2());
}

二、优化gRPC客户端配置:避免全局重试冲突

你当前AddGrpcService中配置的gRPC内置重试策略,会与Polly重试逻辑冲突——内置重试会先尝试重连,只有失败后才抛出异常到Polly,导致无法及时感知服务下线。

1. 移除gRPC内置重试策略

修改AddGrpcService,让Polly完全接管重试和异常处理:

private static void AddGrpcService<T>(IServiceCollection services, Config config) where T : class
{
    SocketsHttpHandler socketsHandler = new SocketsHttpHandler()
    {
        PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,
        KeepAlivePingDelay = TimeSpan.FromSeconds(60),
        KeepAlivePingTimeout = TimeSpan.FromSeconds(30),
        EnableMultipleHttp2Connections = true
    };

    // 移除内置重试策略,仅保留基础方法配置
    MethodConfig defaultMethodConfig = new MethodConfig
    {
        Names = { MethodName.Default }
    };

    ServiceConfig serviceConfig = new() { MethodConfigs = { defaultMethodConfig } };
    services.AddGrpcClient<T>(o => {
        o.Address = new Uri(config.GrpcUrl);
    })
    .ConfigureChannel(o => {
        o.Credentials = GetGrpcClientCredentials(config);
        o.ServiceConfig = serviceConfig;
    })
    .ConfigurePrimaryHttpMessageHandler(() => socketsHandler);
}

三、UI层订阅事件更新界面

UI层通过订阅OnServiceOffline事件,接收服务下线通知并更新界面:

// UI层示例(以WPF为例)
public void Initialize()
{
    // 订阅服务下线事件
    subscriptionService.OnServiceOffline += (serviceName) => {
        Dispatcher.Invoke(() => {
            // 更新UI提示
            StatusTextBlock.Text = $"{serviceName} 连接已断开,正在尝试重连...";
        });
    };
}

四、额外优化:精细区分异常场景

如果需要更精准的服务识别,可以利用gRPC异常的Detail字段(若服务端返回包含服务标识的错误信息):

onRetry: (e, ts) => {
    if (e.Status.Detail.Contains("Service1"))
    {
        OnServiceOffline?.Invoke("Service1");
    }
    logger.Warning("{Service} 连接丢失,{Seconds}秒后重试", serviceName, ts.Seconds);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 21:07:03