How to buffer messages on signal hub and send them when the right client appears?

前端 未结 1 602
北恋
北恋 2021-01-15 05:56

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 th

相关标签:
1条回答
  • 2021-01-15 06:13

    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.

    0 讨论(0)
提交回复
热议问题