如何关闭SignalR中的循环?页面刷新丢数据致循环无法停止
问题分析与解决方案
核心问题
- 线程安全缺失:
_isDisconnected是普通布尔字段,异步/多线程场景下读写不具备原子性,循环可能无法及时感知状态变更。 - 集合操作错误:每次循环都执行
_connectionNewsIdMap.Add(Context.ConnectionId, id),同一连接ID重复添加会抛出异常,导致逻辑中断或混乱。 - 连接生命周期不匹配:刷新页面会创建新的SignalR连接,旧连接的循环任务未被正确终止,仍会持续执行。
修复代码
用CancellationTokenSource替代布尔字段管理循环取消,同时调整集合操作时机:
private CancellationTokenSource _cts; public async Task RecordAccess(int id) { // 绑定连接断开的内置令牌,确保连接断开时自动触发取消 _cts = CancellationTokenSource.CreateLinkedTokenSource(Context.ConnectionAborted); // 仅首次调用时添加连接映射,避免重复操作 if (!_connectionNewsIdMap.ContainsKey(Context.ConnectionId)) { _connectionNewsIdMap.Add(Context.ConnectionId, id); Context.Items["TempNewsId"] = id; } try { while (!_cts.Token.IsCancellationRequested) { await Clients.All.SendAsync("ReceivedRecord", id, _username, true, DateTime.Now); // 将取消令牌传入Delay,确保延迟过程中也能响应取消 await Task.Delay(3000, _cts.Token); } } catch (OperationCanceledException) { // 取消异常属于正常流程,无需额外处理 } } public override async Task OnDisconnectedAsync(Exception exception) { if (Context.Items.TryGetValue("TempNewsId", out var tempNewsId)) { var newsId = (int)tempNewsId; _connectionNewsIdMap.Remove(Context.ConnectionId); // 触发循环取消并释放资源 _cts?.Cancel(); _cts?.Dispose(); await Clients.All.SendAsync("ReceivedRecord", 0, _username, false, DateTime.Now); } await base.OnDisconnectedAsync(exception); }
关键优化点
- 可靠的取消机制:结合SignalR内置的
ConnectionAborted令牌,确保连接断开时循环能立即终止。 - 避免重复操作:将连接映射的添加逻辑移到循环外部,防止重复添加抛出异常。
- 资源管理:及时释放取消令牌源,避免资源泄漏。
内容的提问来源于stack exchange,提问作者Trung Hieu
相关产品推荐
相关产品推荐

