问题
I hawe two type of clients connecting my signalR server (ASP.NET Core). Some of them are senders, and some of them are receivers. I need to route messages from senders to the receivers, which is not a problem, but when there is no receivers, I need to somehow buffer messages and not lose them (probably the best is ConcurrentQueue in some kind of a singleton class) but when the first receiver connect, the message buffer needs to start dequeue. Which is the best approach for this?
I created singleton class that wraps arround ConcurrentQueue collection and I enqueue and dequeue messages there. Also I have a separate singleton class which persist collection of the receivers connectionIDs. And I implemented event in this second class that fires event when first receiver connects after the list of receivers was empty but maybe this is not a good approach, I don't know how to use id in Hub, because there is more than one instance of a signalR hub. Second approach is to mark persistance class as controller and inject the ContextHub and message buffer in this class and dequeue buffer from there and directly send messages to the receivers???
回答1:
If I understood well, you want to defer SignalR message sending by using something like a synchronized call in some IHostedService. Here is what I managed to achieve so far.
- Your approach that consists in using a ConcurrentQueue that contains invokable Action delegates to handle the concurrent hub calls is the right one. As you mention, it has to be injected as a singleton.
So here the Queues
class:
public class Queues {
public ConcurrentQueue<Action<IHubContext<MyHub, IMyEvents>>> MessagesQueue { get; set; }
}
- Now we need to capture the
ConnectionId
of the caller so a call can get an answer later.SendMessage
enqueue the necessary action delegate to perform a call against a hub instance as a parameter.
As an example SendMessage
will trigger an answer back to the caller, and BroadcastMessage
will send a message to all clients.
Using a captured hub instance instead would lead to an exception here because the hub will be disposed, quickly. That's why it would be injected later in another class. Have a look on SendMessage_BAD
Here is the MyHub
class and the corresponding IMyEvents
interface:
public interface IMyEvents {
void ReceiveMessage(string myMessage);
}
public class MyHub : Hub<IMyEvents> {
Queues queues;
public MyHub(Queues queues) {
this.queues = queues;
}
public void SendMessage(string message) {
var callerId = Context.ConnectionId;
queues.MessagesQueue.Enqueue(hub => hub.Clients.Client(callerId).ReceiveMessage(message));
}
// This will crash
public void SendMessage_BAD(string message) {
this.callerId = Context.ConnectionId;
queues.MessagesQueue.Enqueue(_ => this.Clients.Client(callerId).ReceiveMessage(message));
}
public void BroadcastMessage(string message) {
queues.MessagesQueue.Enqueue(hub => hub.Clients.All.ReceiveMessage(message));
}
}
- Now, using a naive approach, this code will trigger the message sending a deferred way. (At work, a timer ensure a regular cadence, and the class is an
IHostedService
but it is does not appear here). This class has to be injected as a singleton.
Here the DeferredMessageSender
class:
public class DeferredMessageSender {
Queues queues;
IHubContext<MyHub, IMyEvents> hub;
public DeferredMessageSender(Queues queues, IHubContext<MyHub, IMyEvents> hub) {
this.queues = queues;
this.hub = hub;
}
public void GlobalSend() {
while(queues.MessagesQueue.TryDequeue(out var evt)) {
evt.Invoke(hub);
}
}
}
Hope it helps.
来源:https://stackoverflow.com/questions/55735378/how-to-buffer-messages-on-signal-hub-and-send-them-when-the-right-client-appears