【问题标题】:c# .NET Core SignalR exception when client calls a function (not always happens)客户端调用函数时的 c# .NET Core SignalR 异常(并不总是发生)
【发布时间】:2020-11-17 18:02:06
【问题描述】:

我有以下服务器SignalR Hub:

 public class AlertHub : Hub
    {
        static ConcurrentDictionary<string, string> _users = new ConcurrentDictionary<string, string>();

        public Task SendMessage(string user, string message)
        {
            return Clients.All.SendAsync("BroadcastMessage", user, message);
        }

        public Task SendMessageToUser(string userUid, string user, string message, string totalMessages)
        {
            var clientId = _users.FirstOrDefault(x => x.Value == userUid).Key;
            return Clients.Client(clientId).SendAsync("BroadcastMessage", user, message, totalMessages);
        }

        public static void ClearState()
        {
            _users.Clear();
        }

        public override Task OnConnectedAsync()
        {
            _users.TryAdd(Context.ConnectionId, Context.ConnectionId);

            return base.OnConnectedAsync();
        }

        public override Task OnDisconnectedAsync(Exception exception)
        {
            string userUid;
            _users.TryRemove(Context.ConnectionId, out userUid);

            return base.OnDisconnectedAsync(exception);
        }

        public void SetUserUid(string userUid)
        {
            _users[Context.ConnectionId] = userUid;
        }
    }

在我的客户端上,就在await _signalRConnection.StartAsync(); 之前,我将userUid 设置为我的_users 字典:

//Send user name for this client, so we won't need to send it with every message
                await _signalRConnection.InvokeAsync("SetUserUid", this.State.UserUID);

而且效果很好,没有例外。

然后在我的客户端上,当我尝试发送消息时,我会执行以下操作:(这通常有效,异常​​是间歇性的)

await _signalRConnection.InvokeAsync("SendMessageToUser", model.UserUid.ToString().ToLower(), this.State.UserName, $"{model.UserName} has sent you a message.", countMessagesString);

这正常工作,但有时它开始崩溃,在服务器中显示以下错误:

Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher.? [?] - MESSAGE: Failed to invoke hub method 'SendMessageToUser'.
 System.ArgumentNullException: Value cannot be null. (Parameter 'connectionId')

那是因为当尝试获取 clientId 时,它会在此处返回 null:

 var clientId = _users.FirstOrDefault(x => x.Value == userUid).Key; <-- HERE
return Clients.Client(clientId).SendAsync("BroadcastMessage", user, message, totalMessages);

所以我不知道为什么 _users 字典有时没有密钥。这很奇怪。

关于如何挖掘或解决它的任何线索?

** 更新 **

这就是我所做的:

  public class AlertHubDictionary
    {
        static ConcurrentDictionary<string, string> _users;

        public AlertHubDictionary()
        {
            _users = new ConcurrentDictionary<string, string>();
        }

        public ConcurrentDictionary<string, string> UsersDictionary { get { return _users; } }

        public void ClearUsersDictionary()
        {
            _users.Clear();
        }
    }

然后这个类注册为Singleton DI:

   services.AddSingleton<AlertHubDictionary>();

最后对 Hub 类进行替换:

public class AlertHub : Hub
    {
        private readonly ILogger<AlertHub> _logger;
        private AlertHubDictionary _hubDictionary;
        static ConcurrentDictionary<string, string> _users = new ConcurrentDictionary<string, string>();

        public AlertHub(ILogger<AlertHub> logger, AlertHubDictionary hubDictionary)
        {
            _logger = logger;
            _hubDictionary = hubDictionary;
        }
        public Task SendMessage(string user, string message)
        {
            //Clients.Client("").SendAsync("BroadcastMessage", user, message);
            return Clients.All.SendAsync("BroadcastMessage", user, message);
        }

        public Task SendMessageToUser(string userUid, string user, string message, string totalMessages)
        {
            //var clientId = _users.FirstOrDefault(x => x.Value == userUid).Key;
            var clientId = _hubDictionary.UsersDictionary.FirstOrDefault(x => x.Value == userUid).Key;

            using (var scope = _logger.BeginScope("SCOPED_VALUE"))
            {
                _logger.LogInformation($"Send message to userUid {userUid} - {user} with clientId {clientId}.");

            }


            if (clientId == null)
            {
                // the user is not connected
                return Task.FromResult(0);
            }
            else
            {

                return Clients.Client(clientId).SendAsync("BroadcastMessage", user, message, totalMessages);
            }
        }

        public void ClearState()
        {
            _hubDictionary.ClearUsersDictionary();
            //_users.Clear();

            using (var scope = _logger.BeginScope("SCOPED_VALUE"))
            {
                _logger.LogInformation("_users cleared");

            }

        }

        public override Task OnConnectedAsync()
        {
            _hubDictionary.UsersDictionary.TryAdd(Context.ConnectionId, Context.ConnectionId);
            //_users.TryAdd(Context.ConnectionId, Context.ConnectionId);

            using (var scope = _logger.BeginScope("SCOPED_VALUE"))
            {
                _logger.LogInformation($"ConnectionId {Context.ConnectionId} connected.");

            }

            return base.OnConnectedAsync();
        }

        public override Task OnDisconnectedAsync(Exception exception)
        {
            string userUid;
            _hubDictionary.UsersDictionary.TryRemove(Context.ConnectionId, out userUid);

            using (var scope = _logger.BeginScope("SCOPED_VALUE"))
            {
                _logger.LogInformation($"UserUid {userUid} disconnected.");

            }


            return base.OnDisconnectedAsync(exception);
        }

        public void SetUserUid(string userUid)
        {
            _hubDictionary.UsersDictionary[Context.ConnectionId] = userUid;

            using (var scope = _logger.BeginScope("SCOPED_VALUE"))
            {
                _logger.LogInformation($"Connection {Context.ConnectionId} linked to userUid {userUid}");

            }

            //ClientNameChanged?.Invoke(Context.ConnectionId, userName);
        }
    }

【问题讨论】:

  • 将日志记录到文件中。从添加/清空 _users 的方法记录。您可能会不时失去连接 = 这就是它被清除的原因。

标签: c# signalr signalr-hub asp.net-core-signalr


【解决方案1】:

根据您捕获用户 ID 的方式,您似乎正在尝试将 SignalR 集线器视为单例。根据 Microsoft 的文档,集线器是一个瞬态类:

https://docs.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-3.1#create-and-use-hubs

集线器是暂时的:

不要将状态存储在集线器类的属性中。每个集线器方法 调用在新的集线器实例上执行。

因此,如果您想保留已注册用户 ID 的共享实例,您可能应该将其作为一个单独的类进行管理,该类注册为单例并注入到您的集线器类中,以便可以独立于集线器维护状态.

【讨论】:

  • 太棒了。是的,这完全有道理。我可以查看如何实现它的任何链接?
  • @VAAA - 你可以简单地把 Dictionary 移到它自己的类中,在启动时注册它 service.AddSingleton&lt;YourUserCacheClass&gt;(),然后使用构造函数注入将它注入到集线器的构造函数中,如 @987654322 所述@
  • 这里有一个来自 Microsoft 的非常相似的独立类示例,尽管标记为内部,而您可以使用 public:github.com/dotnet/extensions/blob/release/3.1/src/…
  • 太欣赏了,我试试看
  • 刚刚更新了我所做的更改,我要试试。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多