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

如何关闭SignalR中的循环?页面刷新丢数据致循环无法停止

问题分析与解决方案

核心问题

  1. 线程安全缺失:_isDisconnected是普通布尔字段,异步/多线程场景下读写不具备原子性,循环可能无法及时感知状态变更。
  2. 集合操作错误:每次循环都执行_connectionNewsIdMap.Add(Context.ConnectionId, id),同一连接ID重复添加会抛出异常,导致逻辑中断或混乱。
  3. 连接生命周期不匹配:刷新页面会创建新的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 21:22:41