Blazor Server应用SignalR连接意外关闭问题排查与优化咨询
Blazor Server SignalR连接异常排查与稳定性优化
问题背景
开发基于Blazor Server的气象站应用,通过SignalR将树莓派3B(arm64架构)上的传感器实时数据推送到客户端。目前客户端无法接收数据,连接被服务器意外关闭,且Closed事件处理中出现ObjectDisposedException。
关键日志
info: Microsoft.AspNetCore.Http.Connections.Client.HttpConnection[6] HttpConnection Disposed. dbug: Microsoft.AspNetCore.SignalR.Client.HubConnection[46] Canceling all outstanding invocations. dbug: Microsoft.AspNetCore.SignalR.Client.HubConnection[21] HubConnection stopped. dbug: Microsoft.AspNetCore.SignalR.Client.HubConnection[51] Invoking the Closed event handler. fail: WeatherStationBlazor.Components.Pages.Weather[0] Connection closed: fail: Microsoft.AspNetCore.SignalR.Client.HubConnection[27] An exception was thrown in the handler for the Closed event. System.ObjectDisposedException: Cannot access a disposed object. Object name: 'Microsoft.AspNetCore.SignalR.Client.HubConnection'.
相关代码
Program.cs
using Microsoft.AspNetCore.Http.Connections; using WeatherStationBlazor.Components; using WeatherStationBlazor.Data; namespace WeatherStationBlazor { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); builder.Services.AddHostedService<SensorDataBackgroundService>(); builder.Services.AddSingleton<Bme280Service>(); builder.Services.AddSignalR(hubOptions => { hubOptions.EnableDetailedErrors = true; hubOptions.KeepAliveInterval = TimeSpan.FromMinutes(1); }); builder.WebHost.ConfigureKestrel(options => { options.ListenAnyIP(5000); }); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAntiforgery(); app.MapRazorComponents<App>() .AddInteractiveServerRenderMode(); app.MapHub<SensorHub>("/sensorHub", options => { options.Transports = HttpTransportType.WebSockets | HttpTransportType.LongPolling; }); app.Run(); } } }
客户端Blazor组件(Weather.razor)
@page "/weather" @using Microsoft.AspNetCore.SignalR.Client @inject WeatherStationBlazor.Data.Bme280Service Bme280Service @inject NavigationManager NavigationManager @inject ILogger<Weather> Logger @implements IAsyncDisposable <h3>Real-Time Weather Data</h3> @if (!dataLoaded) { <p>Loading...</p> } else { <p>Temperature: @temperature?.ToString("F2") °C</p> <p>Humidity: @humidity?.ToString("F2") %</p> <p>Pressure: @pressure?.ToString("F2") hPa</p> } @code { private double? temperature; private double? humidity; private double? pressure; private bool dataLoaded = false; private HubConnection? hubConnection; protected override async Task OnInitializedAsync() { try { Logger.LogInformation("Initializing Hub connection..."); hubConnection = new HubConnectionBuilder() .WithUrl(NavigationManager.ToAbsoluteUri("/sensorHub")) .WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.Zero, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5) }) .ConfigureLogging(logging => { logging.AddConsole(); logging.SetMinimumLevel(LogLevel.Debug); }) .Build(); hubConnection.Closed += async (error) => { Logger.LogError($"Connection closed: {error?.Message}"); await Reconnect(); }; hubConnection.On<double, double, double>("ReceiveSensorData", async (temp, hum, pres) => { Logger.LogInformation($"Received data: Temperature: {temp}, Humidity: {hum}, Pressure: {pres}"); temperature = temp; humidity = hum; pressure = pres; await InvokeAsync(StateHasChanged); }); await StartHubConnectionAsync(); // Load initial data Logger.LogInformation("Loading initial sensor data..."); var data = await Bme280Service.ReadSensorDataAsync(); temperature = data.temperature; humidity = data.humidity; pressure = data.pressure; dataLoaded = true; } catch (Exception ex) { Logger.LogError($"Error initializing Hub connection: {ex.Message}"); dataLoaded = false; } } private async Task StartHubConnectionAsync() { if (hubConnection != null && hubConnection.State == HubConnectionState.Disconnected) { try { await hubConnection.StartAsync(); Logger.LogInformation("Hub connection started successfully."); } catch (Exception ex) { Logger.LogError($"Error starting Hub connection: {ex.Message}"); await Task.Delay(5000); // Wait for 5 seconds before retrying await StartHubConnectionAsync(); } } } private async Task Reconnect() { Logger.LogInformation("Attempting to reconnect..."); while (hubConnection?.State == HubConnectionState.Disconnected) { try { await hubConnection.StartAsync(); Logger.LogInformation("Reconnected to Hub."); return; } catch (Exception ex) { Logger.LogError($"Reconnection attempt failed: {ex.Message}"); await Task.Delay(2000); // Wait for 2 seconds before retrying } } } public async ValueTask DisposeAsync() { if (hubConnection != null) { var connection = hubConnection; hubConnection = null; try { await connection.StopAsync(); Logger.LogInformation("Hub connection stopped successfully."); } catch (Exception ex) { Logger.LogError($"Error stopping Hub connection: {ex.Message}"); } finally { await connection.DisposeAsync(); Logger.LogInformation("Hub connection disposed."); } } } }
已尝试方案
- 配置
WithAutomaticReconnect()实现自动重连 - 调整SignalR的
KeepAliveInterval与ClientTimeoutInterval参数 - 确保组件销毁时正确释放
HubConnection
问题解答
1. 服务器意外关闭SignalR连接的可能原因
- 资源限制:树莓派3B内存/CPU资源有限,当传感器数据推送频繁或后台服务占用过高时,Kestrel可能因资源不足主动断开连接
- 超时配置不匹配:当前
KeepAliveInterval设为1分钟,但客户端默认超时(30秒)早于服务器心跳,导致连接被判定为失效 - WebSocket兼容性:arm64环境下Kestrel的WebSocket实现可能存在兼容性问题,降级为LongPolling可验证此问题
- 异常未处理:SensorHub或后台推送服务中存在未捕获的异常,导致连接被服务器终止
- 网络波动:树莓派所在网络不稳定,出现丢包或延迟导致连接中断
2. 确保客户端持续接收数据的修复措施
- 修复Closed事件的ObjectDisposedException:在Reconnect方法中先检查连接是否已被释放,避免访问已销毁的对象:
private async Task Reconnect() { Logger.LogInformation("Attempting to reconnect..."); while (true) { if (hubConnection == null) break; if (hubConnection.State != HubConnectionState.Disconnected) break; try { await hubConnection.StartAsync(); Logger.LogInformation("Reconnected to Hub."); return; } catch (Exception ex) { Logger.LogError($"Reconnection attempt failed: {ex.Message}"); await Task.Delay(2000); } } } - 统一重连逻辑:移除自定义Reconnect方法,依赖
WithAutomaticReconnect()的内置重连机制,同时监听Reconnected事件重新订阅:hubConnection.Reconnected += async (connectionId) => { Logger.LogInformation($"Reconnected with connection ID: {connectionId}"); hubConnection.On<double, double, double>("ReceiveSensorData", OnReceiveSensorData); }; private async Task OnReceiveSensorData(double temp, double hum, double pres) { Logger.LogInformation($"Received data: Temperature: {temp}, Humidity: {hum}, Pressure: {pres}"); temperature = temp; humidity = hum; pressure = pres; await InvokeAsync(StateHasChanged); } - 匹配超时配置:在客户端连接时设置与服务器一致的心跳和超时时间:
hubConnection = new HubConnectionBuilder() .WithUrl(NavigationManager.ToAbsoluteUri("/sensorHub"), options => { options.ClientTimeoutInterval = TimeSpan.FromMinutes(2); options.KeepAliveInterval = TimeSpan.FromMinutes(1); }) .WithAutomaticReconnect() .Build(); }
3. Blazor Server中提升SignalR连接稳定性的配置与模式
- 复用HubConnection实例:通过单例服务注入全局HubConnection,避免每个组件创建独立连接,减少连接开销
- 优化资源占用:在树莓派上限制Kestrel的并发连接数,避免资源耗尽:
builder.WebHost.ConfigureKestrel(options => { options.ListenAnyIP(5000); options.Limits.MaxConcurrentConnections = 10; options.Limits.MaxConcurrentUpgradedConnections = 10; }); - 异常隔离:在后台推送服务中捕获数据推送异常,避免单条数据推送失败导致连接中断:
try { await _hubContext.Clients.All.SendAsync("ReceiveSensorData", temp, hum, pres); } catch (Exception ex) { _logger.LogError($"Failed to send sensor data: {ex.Message}"); } - 禁用不必要的中间件:若未配置SSL证书,关闭
app.UseHttpsRedirection(),避免因重定向导致WebSocket连接失败 - 实时状态监控:通过
hubConnection.StateChanged事件监控连接状态,及时更新UI提示用户连接状态变化
内容的提问来源于stack exchange,提问作者kamillo122
相关产品推荐
相关产品推荐

