locked
How can I properly handle backend service events in SignalR? RRS feed

  • Question

  • User-916095149 posted

    I have some doubts with the following code that implements a SignalR endpoint that receives and sends messages.

    Basically the `ISendValuesService` produces new values and raises a `NewValueRegistered` event whenever it does. Upon this event the SignalR hub sends a message to all connected clients that have registered for the value's ID.

    The communication itself is not the problem though.

    The issue is that the clients can call the `SubscribeToVariables` and `UnsubscribeFromVariables` during runtime. For each of these calls a new Hub object is instantiated (as per design of SignalR). When this happens, the new Hub would also subscribe to the event and any new values would be sent multiple times to the clients since there are now multiple

    I currently have limited the registration to the event with a static bool variable (`_eventRegistered` in the code below), but this feels kind of clumsy.

    Is there a recommended pattern to handle such a case for Signal without skipping repeated event registration in subsequent Hub instances?

    public class ValueHub : Hub
    {
        private ISendValuesService _sendValuesService;
        private IHubContext<ValueHub> _hubContext;
        private static bool _eventRegistered;
        private object _eventRegistrationLock = new object();
    
        public ValueHub(ISendValuesService sendValuesService, IHubContext<ValueHub> hubContext)
        {
            _hubContext = hubContext;
    
            _sendValuesService = sendValuesService;
    
            lock (_eventRegistrationLock)
            {
                if (!_eventRegistered)
                {
                    _sendValuesService.NewValueRegistered += OnNewValueRegistered;
                    _eventRegistered = true;
                }
            }
        }
    
        private void OnNewValueRegistered(object sender, NewValueRegisteredEventArgs e)
        {
            _ = Task.Run(() => HandleNewValueTask(e));
        }
    
        private void HandleNewValueTask(NewValueRegisteredEventArgs e)
        {
            var valueMsg = $"\"ID\":{e.Id},\"Value\":\"{e.Value,3}\",\"Timestamp\":\"{e.RegistrationTime}\"";
    
            Parallel.ForEach(_sendValuesService.Subscriptions,
                new ParallelOptions() { MaxDegreeOfParallelism = 4 },
                subscription =>
                {
                    if (subscription.Value.Contains(e.Id))
                    {
                        _hubContext.Clients.Client(subscription.Key).SendCoreAsync("NewValue", new[] { valueMsg });
                    }
                });
        }
    
        public async Task SubscribeToVariables(IEnumerable<int> variableIdsToRegister)
        {
            //Save the connectionId since the Context might be disposed before the task is started
            var connectionId = Context.ConnectionId.ToString();
    
            Debug.WriteLine($"Register variables for connection {connectionId}.");
    
            await Task.Run(() =>
                           _sendValuesService.SubscribeToVariables(connectionId, variableIdsToRegister)
                        ); 
        }
    
        public async Task UnsubscribeFromVariables(IEnumerable<int> variableIdsToUnregister)
        {
            //Save the connectionId since the Context might be disposed before the task is started
            var connectionId = Context.ConnectionId.ToString();
    
            Debug.WriteLine($"Unregister variables for connection {connectionId}.");
    
            await Task.Run(() =>
                           _sendValuesService.UnsubscribeFromVariables(Context.ConnectionId, variableIdsToUnregister)
                        );
        }
    
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
        }
    }

    Wednesday, February 5, 2020 10:53 AM

All replies

  • User283571144 posted

    Hi msdrit ,

    According to your description, I couldn't understand your requirement clearly. 

    Do you mean you want to send the value message when the client connect to your signlar hub firstly?  

    Without your detail description for your  requirement, we couldn't provide the right solution for you. 

    Best Regards,

    Brando

    Thursday, February 6, 2020 7:17 AM
  • User-916095149 posted

    Hi Brando,

    the issue is if I do not use the boolean variable every time a client calls the `SubscribeToVariables` or `UnsubscribeFromVariables` methods a Hub instance is created.

    Each of these instances leaves a registration to the NewValue event and this registration stays alive even if the hub instance should have died, because method call is complete.

    So the following happens.

    Client connects - Hub instance 1; event handler registered first time (from now on one message is sent to the client when the event is triggered)

    Clients calls Subscribe - Hub instance 2; event handler registered second time (from now on two messages are sent to the client when the event is triggered)

    Clients calls Unubscribe - Hub instance 3; event handler registered third time (from now on three messages are sent to the client when the event is triggered)

    Clients calls Subscribe - Hub instance 4; event handler registered fourth time (from now on four messages are sent to the client when the event is triggered)

    I also tried unsubscribing in the Dispose method of the hub but that didn't work out either.

    My expectation was that only the hub first hub instance would live long enough to send messages when the event is triggered.

    Thursday, February 6, 2020 8:31 AM
  • User-916095149 posted

    I have added a POC as Github repo:

    https://github.com/msdeibel/SignalRIssue/tree/master

    Wednesday, February 19, 2020 6:31 AM