.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
相关产品推荐
相关产品推荐

