SignalR continuous messaging

2019-06-21 22:42发布

问题:

I have a web project which requires stats/logs on a web page to be updated from an external soap service. The approach I have decided to take with this is to use signalR, by having code that would execute a service method and return the results to all connected clients. This code will be continuously executed, with a delay between service calls.

I am having trouble putting all of the pieces together, probably because I am not sure where everything should go! Here is a rough breakdown at what I have done so far

class Data { ... }

interface IDataService
{
    Task GetDataAsync(TimeSpan interval, CancellationToken cancellationToken, Action<Data> callback);
}

class DataService : IDataService
{
    private readonly ExternalService _externalService;

    public async Task GetDataAsync(TimeSpan interval, CancellationToken cancellationToken, Action<Data> callback)
    {
         while (!cancellationToken.IsCancellationRequested)
         {
              var data = await this._externalService.GetData();
              callback(data);

              await Task.Delay(interval).ConfigureAwait(false);
         }
    }
}

So above is the logic that gets the data from the external service, and executes a callback. As for signalR, the only thing I have done is as follows

public class DataHub : Hub
{
    private readonly IDataService _service;

    public DataHub(IDataService service)
    {
        this._service = service;
    }

    public async Task GetData()
    {
        var tenSeconds = new TimeSpan(0, 0, 10);
        var token = new CancellationToken();
        await _service.GetDataAsync(tenSeconds, token, d =>
        {
             // signal all connected clients
        });
    }
}

The GetData() method can be called when clients connect (if it isn't already running) and the token can be cancelled when there are no more clients connected to the hub.

(Potential) Problems:

  1. SignalR hubs are not singletons are they? I assume they are created when needed and disposed when not needed anymore (request has finished). In which case I will have multiple instances of the GetData method running (unless I make the DataService class a singleton/injected as a singleton).
  2. A hub doesn't feel like the right place to do this. A hub should be treated like an MVC/Web Api controller? So a request comes in, hub handles it, request is finished. End of story.
  3. Another way to do this would be to pass the hub into the dataservice. And then it can call the clients. But I don't really want to be passing around IHubContext objects. I want to keep the service as simple as possible.
  4. Would an ASP.NET background task be more appropriate here?

Would be grateful if someone can point me to the right/best approach!

回答1:

I have made a library that decouples your domain from SignalR, it will solve problem 1-3 for you.

Please have a look here https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/wiki

Problem 4, you should absolutely use a ASP.NET background worker over a standard thread, otherwise ASP won't know it exists and recycle the app etc



回答2:

1,2,4: I would use a background task and then take advantage of the Connectionmanager class to retrieve the appropriate hub. Here you find more info about how to make calls from outside the hub.

3: To keep my services clean I usually register so called message services. My data service then calls a method from the message service which internally resolves a hub and propagates the message. On this way I keep my data services tidy as they only call a specific message service method (typically a one-liner). My data services dont need any knowledge of what my message service is doing.



回答3:

I have managed to solve this issue by using the IRegisteredObject interface in the System.Web.Hosting namespace. So my scheduled task is:

public class DataTask : IRegisteredObject
{
     private readonly IGlobalData _global;
     private readonly IDataService _service;
     private readonly IHubContext _hub;

     private Timer _timer;

     public DataTask(IGlobalData global, IDataService service, IHubContext hub)
     {
          this._global = global;
          this._service = service;
          this._hub = hub;

          var interval = new TimeSpan(0, 0, 10);
          this._timer = new Timer(updateClients, null, TimeSpan.Zero, interval);

          // register this task with asp.net
          HostingEnvironment.RegisterObject(this);
     }

     public void Stop(bool immediate)
     {
          _timer.Dispose();

          HostingEnvironment.UnregisterObject(this);
     }

     private async void updateClients(object state)
     {
          var result = await this._service.GetData();
          // call the hub
          this._hub.Clients.All.updateData(result);
     }
}

I did have quite a few issues! But this was due to the custom dependency resolver I was using for signalR (the problem was the client js function was not being called from this task). Once that was resolved, everything works as expected.