3

I wrote a web application in ASP.NET that exchange data with a C# program via SignalR. Here the relevant parts of my code. Of course if I forgot something important to address my issue, please ask in the comments.

ASP.NET

HubClientManager.cs

using Microsoft.AspNet.SignalR.Client;
using System;
using System.Threading.Tasks;
using System.Diagnostics;

namespace MyProject
{
    public class HubClientManager
    {
        public String GetServerURI()
        {
            return String.Concat("http://192.168.1.103", ":8080", "/signalr");
        }

        public IHubProxy GetHub()
        {
            var hubConnection = new HubConnection(GetServerURI());
            var hub = hubConnection.CreateHubProxy("liveHub");

            try
            {
                var task = hubConnection.Start();
                Task.WhenAll(task).Wait();
            }
            catch (Exception e)
            {
                Debug.WriteLine(e);
            }
            return hub;
        }
    }
}

Index.cshtml

<script src="@ViewBag.serverURI/hubs"></script>
<script>
    $(function () {   
        $.connection.hub.url = "@ViewBag.serverURI";
        var hub = $.connection.liveHub;

        hub.client.orderStatus = function (opened, suspended, running, closed, cancelled) {
            $("#cntOpened").text(opened);
            $("#cntSuspended").text(suspended);
            $("#cntRunning").text(running);
            $("#cntClosed").text(closed);
            $("#cntCancelled").text(cancelled);
        };

        $.connection.hub.logging = true;

        $.connection.hub.start().done(function () { });

        $.connection.hub.error(function (error) {
            console.log('SignalR error: ' + error)
        });
    });
</script>

MachinesController.cs

public class MachinesController : Controller
{
    private HubClientManager _manager = new HubClientManager();
    private IHubProxy _hub;

    public MachinesController()
    {
        _hub = _manager.GetHub();
    }

    // GET: Machines
    public ActionResult Index()
    {
        ViewBag.serverURI = _manager.GetServerURI();
        return View(...);
    }

    ...
}

C#

Hub.cs

namespace MyProject.Hubs
{
    public class LiveHub : Hub
    {
        private readonly Erp _erp;

        public LiveHub(Erp erp)
        {
            _erp = erp;
            _erp.HubSendOrderStatus += Erp_HubSendOrderStatus;
        }

        private void Erp_HubSendOrderStatus(int arg1, int arg2, int arg3, int arg4, int arg5)
        {
            Clients.All.orderStatus(arg1, arg2, arg3, arg4, arg5);
        }

        ...    

        public override Task OnConnected()
        {
            _erp.SendOrderStatus();       
            return base.OnConnected();
        }
    }
}

All works fine: I can show data int the web page coming from the C# application (from the Erp class, and send commands from the web page back to the engine application. Here I reported only a small set of functions, but they should be enough to understand what happens.

Perhaps I'm blind and I cannot see my mistake looking at the examples. Anyway, every time I refresh a page in the browser or even I load another page of the same application (that of course shares the same JavaScript code above) I receive more and more SignalR messages! I mean, if the first time I receive the orderStatus message every 10 seconds, after a reload (or a page change) I receive 2 messages every 10 seconds. Another refresh and they become 3, and so on... after some time the whole system become unusable because it receives thousands of messages at once.

I'm aware there's the OnDisconnected() callback but it seems it's called by the framework to notify a client has disconnected (and I'm not interested to know it).

UPDATE

I cannot post the whole Erp class because it 3k+ lines long... anyway, most of the code does all other kind of stuff (database, field communications, etc...). Here the only functions involved with the hub:

public event Action<int, int, int, int, int> HubSendOrderStatus;

public void SendOrderStatus()
{
    using (MyDBContext context = new MyDBContext())
    {
        var openedOrders = context.Orders.AsNoTracking().Count(x => x.State == OrderStates.Opened);
        var suspendedOrders = context.Orders.AsNoTracking().Count(x => x.State == OrderStates.Suspended);
        var runningOrders = context.Orders.AsNoTracking().Count(x => x.State == OrderStates.Running || x.State == OrderStates.Queued);
        var closedOrders = context.Orders.AsNoTracking().Count(x => x.State == OrderStates.Completed);
        var cancelledOrders = context.Orders.AsNoTracking().Count(x => x.State == OrderStates.Cancelled);

        HubSendOrderStatus?.Invoke(openedOrders, suspendedOrders, runningOrders, closedOrders, cancelledOrders);
    }
}

public async Task ImportOrdersAsync()
{
    // doing something with I/O file
    SendOrderStatus();
}

public void JobImportOrders()
{
    Timer t = null;
    t = new Timer(
    async delegate (object state)
    {
        t.Dispose();
        await ImportOrdersAsync();
        JobImportOrders();
    }, null, 10 * 1000, -1);
}

public Erp()
{
    // initialize other stuff
    JobImportOrders();
}

EDIT

AutofacContainer.cs

public class AutofacContainer
{
    public IContainer Container { get; set; }
    public AutofacContainer()
    {
        var builder = new ContainerBuilder();
        var config = new HubConfiguration();
        builder.RegisterHubs(Assembly.GetExecutingAssembly()).PropertiesAutowired();
        builder.RegisterType<Erp>().PropertiesAutowired().InstancePerLifetimeScope();
        Container = builder.Build();
        config.Resolver = new AutofacDependencyResolver(Container);
    }
}
Mark
  • 4,338
  • 7
  • 58
  • 120
  • Is there any chance that you open same page (where you send message) in many tabs? Because I sometimes forget that as well. – Alex - Tin Le Mar 05 '20 at 18:28
  • Are you talking about the browser's tabs? No, it's the very same tab which I refresh (F5) or click a link to reach another View of my application. But in any case, even if I open say 10 tabs (or 10 different browsers) I should receive **one** message for each connection. – Mark Mar 05 '20 at 18:42
  • Can you try to unregister the event handler and register again? I cant put code in comment. Will put in answer. – Alex - Tin Le Mar 05 '20 at 19:08
  • Can you post your Erp class code? – Alex - Tin Le Mar 05 '20 at 20:23
  • @Alex-TinLe, yes question updated. Thanks. – Mark Mar 06 '20 at 03:31
  • Can you put a breakpoint at line "new Timer()" and check if it hit every time you refresh browser or not? I think you not register Erp as singleton. – Alex - Tin Le Mar 06 '20 at 07:39
  • @Alex-TinLe if it could help I added the `AutofacContainer.cs` code. No, the `new Timer()` is called only when it fires. And it makes sense because `Erp` is not inside the ASP application. The problem is already there when I call `_erp.SendOrderStatus(); ` inside the `OnConnected()` handler. – Mark Mar 06 '20 at 10:45
  • Can you change to register your Erp as Singleton (.SingleInstance()) instead of InstancePerLifetimeScope() – Alex - Tin Le Mar 06 '20 at 10:49
  • @Alex-TinLe, I'm very sorry to say even this tip didn't change the behavior. – Mark Mar 06 '20 at 11:19
  • haha, no worries, without full source code, all we can do is isolating the problem. For testing purpose, in your JobImportOrders(), can you replace all its content with only "SendOrderStatus()". – Alex - Tin Le Mar 06 '20 at 11:52

2 Answers2

1

Following the suggestions of "Alex - Tin Le" I discovered the handlers weren't removed with:

_erp.HubSendOrderStatus -= Erp_HubSendOrderStatus;

I fixed this weird behavior checking if the handler is already registered:

if (!_erp.HubSendOrderStatus_isRegistered())  _erp.HubSendOrderStatus += Erp_HubSendOrderStatus;

The HubSendOrderStatus_isRegistered function is this:

public bool HubSendOrderStatus_isRegistered()
{
    return HubSendOrderStatus != null;
}

I solved the initial problem: no more flood of messages when making new connections.

The last bit I don't understand is why it sends out 2 messages at time no matter the number of active connections. Debugging both the Javascript and the server code I noticed that a new connection is made on $.connection.hub.start().done(function () { }); as expected. But another was already created when refreshing a page, even before any available breakpoint. But removing the "explicit" one leads to receive no messages.

Mark
  • 4,338
  • 7
  • 58
  • 120
0

Can you try this?

_erp.HubSendOrderStatus -= Erp_HubSendOrderStatus;
_erp.HubSendOrderStatus += Erp_HubSendOrderStatus;

Normally, I always do this to avoid registering same handler many times.

Alex - Tin Le
  • 1,982
  • 1
  • 6
  • 11