[英]c# .NET Core SignalR exception when client calls a function (not always happens)
我有以下服務器SignalR
集線器:
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);
}
}
根據您捕獲用戶 ID 的方式,您似乎試圖將 SignalR 集線器視為單例。 根據 Microsoft 的文檔,集線器是一個瞬態類:
https://docs.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-3.1#create-and-use-hubs
集線器是暫時的:
不要將狀態存儲在集線器類的屬性中。 每個集線器方法調用都在新的集線器實例上執行。
因此,如果您想保留已注冊用戶 ID 的共享實例,您可能應該將其作為一個單獨的類進行管理,該類注冊為單例並注入到您的集線器類中,以便可以獨立於集線器來維護狀態。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.