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

ASP.NET Core SignalR后台服务调用CheckForUpdates方法失效排查

问题分析与解决方案

核心错误点

你遇到的问题根源在于两个关键误解:

  1. IHubContext的调用逻辑错误:_hubContext.Clients.All.CheckForUpdates() 是向客户端发送名为CheckForUpdates的消息,而非调用服务端NotificationManager类中的CheckForUpdates方法。你的客户端未实现该消息监听,且服务端方法不会因此触发。
  2. 后台服务无HttpContext:后台服务UpdateService运行在无HTTP请求的环境中,NotificationManager里通过_httpContextAccessor.HttpContext.User获取用户ID会返回null,直接导致后续检查逻辑无法执行。

修复步骤

1. 提取检查逻辑为独立服务

将更新检查的核心逻辑从Hub中抽离,避免耦合并支持多场景调用:

public interface IUpdateChecker
{
    Task CheckAndSendUpdatesAsync(IHubContext<NotificationManager, INotificationManager> hubContext, string userId);
}

public class UpdateChecker : IUpdateChecker
{
    private readonly UserConnectionManager _userConnectionManager;
    private readonly YourDbContext _db;

    public UpdateChecker(UserConnectionManager userConnectionManager, YourDbContext db)
    {
        _userConnectionManager = userConnectionManager;
        _db = db;
    }

    public async Task CheckAndSendUpdatesAsync(IHubContext<NotificationManager, INotificationManager> hubContext, string userId)
    {
        var connectionIds = _userConnectionManager.GetConnectionsForUser(userId);
        if (connectionIds == null || !connectionIds.Any())
            return;

        var lastCheckedTime = await GetLastCheckedTimeAsync(userId);
        var updatedEntities = _db.Table
            .Where(e => e.EditedDate > lastCheckedTime).ToList();

        if (updatedEntities.Any())
        {
            foreach (var entity in updatedEntities)
            {
                await hubContext.Clients.User(userId).EntityUpdated(entity.ReferenceNumber);
            }
            await UpdateLastCheckedTimeAsync(userId, DateTime.UtcNow);
        }
        else
        {
            var getLabel = "Stringval";
            await hubContext.Clients.User(userId).NoChangesFound(getLabel);
        }
    }

    // 实现用户最后检查时间的获取与更新逻辑
    private async Task<DateTime> GetLastCheckedTimeAsync(string userId)
    {
        // 示例:从数据库/缓存获取用户的最后检查时间
        return DateTime.UtcNow.AddMinutes(-10);
    }

    private async Task UpdateLastCheckedTimeAsync(string userId, DateTime newTime)
    {
        // 示例:更新数据库/缓存中用户的最后检查时间
    }
}

2. 注册新服务

在Startup.cs中添加服务注册:

services.AddScoped<IUpdateChecker, UpdateChecker>();

3. 修改NotificationManager

调整Hub类,注入独立服务并移除对HttpContext的依赖:

public class NotificationManager : Hub<INotificationManager>
{
    private readonly IUpdateChecker _updateChecker;

    public NotificationManager(IUpdateChecker updateChecker)
    {
        _updateChecker = updateChecker;
    }

    public async Task CheckForUpdates()
    {
        // 从SignalR上下文获取当前用户ID,而非HttpContext
        var userId = Context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        if (string.IsNullOrEmpty(userId))
            return;

        await _updateChecker.CheckAndSendUpdatesAsync(Context.GetHubContext<NotificationManager, INotificationManager>(), userId);
    }
}

4. 修改UpdateService

让后台服务直接调用独立检查服务,遍历在线用户推送更新:

public class UpdateService : BackgroundService
{
    private readonly IHubContext<NotificationManager, INotificationManager> _hubContext;
    private readonly IUpdateChecker _updateChecker;
    private readonly UserConnectionManager _userConnectionManager;

    public UpdateService(IHubContext<NotificationManager, INotificationManager> hubContext, 
                         IUpdateChecker updateChecker, 
                         UserConnectionManager userConnectionManager)
    {
        _hubContext = hubContext;
        _updateChecker = updateChecker;
        _userConnectionManager = userConnectionManager;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                Console.WriteLine($"UpdateService Ran at: {DateTime.Now}");

                // 获取所有在线用户ID,批量检查更新
                var onlineUserIds = _userConnectionManager.GetAllOnlineUserIds();
                foreach (var userId in onlineUserIds)
                {
                    await _updateChecker.CheckAndSendUpdatesAsync(_hubContext, userId);
                }

                await Task.Delay(10000, stoppingToken);
            }
            catch (Exception ex)    
            {
                Console.WriteLine(ex);
            }
        }
    }
}

5. 完善前端代码

确保客户端监听服务端推送的消息,并可主动触发检查:

// 连接成功后监听服务端消息
connection.start().then(function () {
    console.log("Connection started");

    // 监听实体更新通知
    connection.on("EntityUpdated", function(entityRef) {
        console.log("实体更新:", entityRef);
        // 这里添加前端通知逻辑
    });

    // 监听无更新通知
    connection.on("NoChangesFound", function(label) {
        console.log("无更新:", label);
    });

    // 可选:主动触发一次检查
    connection.invoke("CheckForUpdates").catch(function(err) {
        console.error(err.toString());
    });
}).catch(function (err) {
    console.error(err.toString());
});

内容的提问来源于stack exchange,提问作者ScuffedCoder

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 19:27:12