SignalR HubConnection无法更新服务器推送数据的WinForms客户端问题
服务器端通过Background Service + SignalR Hub向所有客户端推送实时时间,客户端基于WinForms开发。
- 服务器日志显示时间推送正常:
Worker running at: 08/16/2023 14:44:29 info: Pr.WebApi.HostedServices.TimeHostedService[0] Worker running at: 08/16/2023 14:44:30 info: Pr.WebApi.HostedServices.TimeHostedService[0] Worker running at: 08/16/2023 14:44:31 info: Pr.WebApi.HostedServices.TimeHostedService[0] Worker running at: 08/16/2023 14:44:32 info: Pr.WebApi.HostedServices.TimeHostedService[0] Worker running at: 08/16/2023 14:44:33 info: Pr.WebApi.HostedServices.TimeHostedService[0] Worker running at: 08/16/2023 14:44:34 info: Pr.WebApi.HostedServices.TimeHostedService[0] Worker running at: 08/16/2023 14:44:35 info: Pr.WebApi.HostedServices.TimeHostedService[0] Worker running at: 08/16/2023 14:44:36 info: Pr.WebApi.HostedServices.TimeHostedService[0] Worker running at: 08/16/2023 14:44:37
- 客户端输出始终显示启动时的固定时间(启动时间14:43:17):
8/16/2023 2:43:17 PM 8/16/2023 2:43:17 PM 8/16/2023 2:43:17 PM 8/16/2023 2:43:17 PM 8/16/2023 2:43:17 PM 8/16/2023 2:43:17 PM 8/16/2023 2:43:17 PM 8/16/2023 2:43:17 PM 8/16/2023 2:43:17 PM 8/16/2023 2:43:17 PM
客户端核心代码如下:
private async void ClientForm_Load(object sender, EventArgs e) { await Task.Run(() => ExecuteAsync()); } public async Task ExecuteAsync() { while (true) { try { if (connection.State != HubConnectionState.Connected) { await connection.StartAsync(); ConnectionStatusLabel.Invoke(delegate { ConnectionStatusLabel.Text = "Connected"; ConnectionStatusLabel.ForeColor = Color.FromArgb(0, 225, 50); }); } connection.On<DateTime>("SendTime", e => { TimeShowLabel.Invoke(delegate { TimeShowLabel.Text = e.ToString("HH:mm:ss"); Debug.WriteLine(e.ToString()); }); }); } catch (Exception ex) { ConnectionStatusLabel.Invoke(delegate { ConnectionStatusLabel.Text = "Not connected"; ConnectionStatusLabel.ForeColor = Color.Red; }); Console.WriteLine(ex.Message); Thread.Sleep(1000); } } }
若将await connection.StartAsync();移出if(connection.State != HubConnectionState.Connected)判断块,客户端可正常更新时间,但会持续抛出System.InvalidOperationException异常:
Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll
Pr.WebApi.HostedServices.TimeHostedService: Information: Worker running at: 08/16/2023 14:48:32Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll
Pr.WebApi.HostedServices.TimeHostedService: Information: Worker running at: 08/16/2023 14:48:33Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll
需要实现无异常且能正确更新时间的功能。
问题根源
- 重复注册回调:
connection.On<DateTime>("SendTime", ...)被放在无限循环中执行,每次循环都会注册一个新的回调函数。当服务器推送时间时,所有注册的回调都会触发,但早期的回调可能捕获了初始时间值,导致显示固定时间。 - 不必要的重复启动连接:移出判断块后反复调用
connection.StartAsync(),而连接已处于Connected状态,触发InvalidOperationException。
修复后的代码
private async void ClientForm_Load(object sender, EventArgs e) { // 仅注册一次回调,避免重复注册导致异常行为 connection.On<DateTime>("SendTime", e => { TimeShowLabel.Invoke(delegate { TimeShowLabel.Text = e.ToString("HH:mm:ss"); Debug.WriteLine(e.ToString()); }); }); await ExecuteAsync(); } public async Task ExecuteAsync() { while (true) { try { // 仅当连接完全断开时才尝试重连 if (connection.State == HubConnectionState.Disconnected) { await connection.StartAsync(); ConnectionStatusLabel.Invoke(delegate { ConnectionStatusLabel.Text = "Connected"; ConnectionStatusLabel.ForeColor = Color.FromArgb(0, 225, 50); }); } // 连接正常时短暂休眠,避免空循环占用CPU await Task.Delay(1000); } catch (Exception ex) { ConnectionStatusLabel.Invoke(delegate { ConnectionStatusLabel.Text = "Not connected"; ConnectionStatusLabel.ForeColor = Color.Red; }); Console.WriteLine(ex.Message); // 重连间隔1秒 await Task.Delay(1000); } } }
关键调整点
- 将
connection.On回调注册移到循环外部,仅执行一次,避免重复注册导致的异常行为。 - 连接状态判断更精确:仅当连接处于
Disconnected状态时才尝试重连,避免对Connected/Connecting状态的连接执行StartAsync。 - 用
await Task.Delay(1000)替代Thread.Sleep(1000),避免阻塞异步线程,更符合异步编程规范。 - 移除
Task.Run:ExecuteAsync本身是异步方法,直接await即可,若担心阻塞UI线程可保留Task.Run但内部逻辑不变。
内容的提问来源于stack exchange,提问作者Duane

