简体   繁体   中英

How to handle updates in the right way using signalr?

I have a client application Angular and a signalR hub, and also I have a service that take a timestamp as a parameter.

I want to invoke a method in the hub when I press on a start button in the client, and when the method is invoked I want to keep listing to all the changes (create a timer) until the client press on the stop button then I will stop the timer.

So I want to ask which is better:

1- Call the invoked method from the client with time stamp and then create a setInterval to call the method in it and when the stop button is pressed I can stop it.

Pros: It is easy to start and stop the timer.

Cons: I am invoking the method each 1 sec, and then I am checking on the client if there are response to update the UI.

2- Invoke the method once and then create a timer for each client on the server and when the client press on stop button I can invoke another method to stop the timer for that client.

Pros: I am checking the timestamp in the hub and I will send the data to the client only if the timeStamp from the service > timeStamp locally

Cons: I actually don't know how to create a timer for each client, so if this is the right way please help me

You are using SignalR for real time data communication. Invoking a method every second is just joking on the SignalR face... So this is not a solution.

The best solution would be using the group feature.

Example:

  1. You start button will add the user to an group.
  2. While your user is on the group it will receive all the data you need. await this.Clients.Group("someGroup").BroadcastMessage(message);
  3. Your stop button will remove the user from the group so it will not receive data anymore.

Some code example on the hub:

public async Task Start()
{
    // Add user to the data group

    await this.Groups.AddToGroupAsync(this.Context.ConnectionId, "dataGroup");
}

public async Task Stop()
{
    // Add user to the data group

    await this.Groups.RemoveFromGroupAsync(this.Context.ConnectionId, "dataGroup");
}

Worker example that sends data to the users that pressed start and receive real time data.

private readonly IHubContext<SignalRHub, ISignalRHub> hub;

private readonly IServiceProvider serviceProvider;

public Worker(IServiceProvider serviceProvider, IHubContext<SignalRHub, ISignalRHub> hub)
{
    this.serviceProvider = serviceProvider;
    this.hub = hub;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        await this.hub.Clients.Group("dataGroup").BroadcastMessage(DataManager.GetData());

        this.Logger.LogDebug("Sent data to all users at {0}", DateTime.UtcNow);

        await Task.Delay(1000, stoppingToken);
    }
}

PS: Where you have the worker, I assume you have some manager that gets the data or something to be sent to the user.

Edit: If you don't want to user worker, you can always just the timer like:

public class TimerManager
{
    private Timer _timer;
    private AutoResetEvent _autoResetEvent;
    private Action _action;
    public DateTime TimerStarted { get; }

    public TimerManager(Action action)
    {
        _action = action;
        _autoResetEvent = new AutoResetEvent(false);
        _timer = new Timer(Execute, _autoResetEvent, 1000, 2000);
        TimerStarted = DateTime.Now;
    }

    public void Execute(object stateInfo)
    {
        _action();
        if((DateTime.Now - TimerStarted).Seconds > 60)
        {
            _timer.Dispose();
        }
    }
}

And then use it somewhere like:

 var timerManager = new TimerManager(() => this.hub.Clients.Group("dataGroup").BroadcastMessage(DataManager.GetData()));

Option #1 isn't available since SignalR exists to remove the need for polling. Frequent polling doesn't scale either. If every client polled the server every 1 second, the web site would end up paying a lot of CPU and bandwidth for nothing. Business people don't like frequent polling either, as all hosters and cloud providers charge for egress.

The SignalR streaming examples use timed notifications as a simple example of streaming notifications using IAsyncEnumerable<T> . In the simplest example, a counter increments every delay milliseconds :

public class AsyncEnumerableHub : Hub
{
    public async IAsyncEnumerable<int> Counter(
        int count,
        int delay,
        [EnumeratorCancellation]
        CancellationToken cancellationToken)
    {
        for (var i = 0; i < count; i++)
        {
            // Check the cancellation token regularly so that the server will stop
            // producing items if the client disconnects.
            cancellationToken.ThrowIfCancellationRequested();

            yield return i;

            // Use the cancellationToken in other APIs that accept cancellation
            // tokens so the cancellation can flow down to them.
            await Task.Delay(delay, cancellationToken);
        }
    }
}

The client can call this action passing the desired delay and just start receiving notifications. SignalR knows this is a stream of notifications because it returns IAsyncEnumerable .

The next, more advanced example uses Channels to allow the publisher method WriteItemsAsync to send a stream of notifications to the hub.

The action itself is simpler, it just returns the Channel's reader:

public ChannelReader<int> Counter(
    int count,
    int delay,
    CancellationToken cancellationToken)
{
    var channel = Channel.CreateUnbounded<int>();

    // We don't want to await WriteItemsAsync, otherwise we'd end up waiting 
    // for all the items to be written before returning the channel back to
    // the client.
    _ = WriteItemsAsync(channel.Writer, count, delay, cancellationToken);

    return channel.Reader;
}

The publisher method writes to the ChannelWriter instead of returning an IAsyncEnumerable :

private async Task WriteItemsAsync(
    ChannelWriter<int> writer,
    int count,
    int delay,
    CancellationToken cancellationToken)
{
    Exception localException = null;
    try
    {
        for (var i = 0; i < count; i++)
        {
            await writer.WriteAsync(i, cancellationToken);

            // Use the cancellationToken in other APIs that accept cancellation
            // tokens so the cancellation can flow down to them.
            await Task.Delay(delay, cancellationToken);
        }
    }
    catch (Exception ex)
    {
        localException = ex;
    }

    writer.Complete(localException);
}

This method can easily be in a different class. All that's needed is to pass ChannelWriter to the publisher.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM