signalrasp.net-core-signalrreconnect

SignalrR Hub Constant Dispose


I have an MS .NET SignalR configured. It is an internal HUB and is only connected via a desktop application. The Hub when created constantly gets disposed and when I attempt to communicate with a client from the hub I get an already disposed error. I can communicate from the client to the hub so is unclear why communicating from the hub to the client is failing. Does anyone have details of why this would occur?

My investigation did not identify the cause. The one thing I noticed was that the hub Dispose operation is called constantly [after a connection has been made]. There is only 1 connection request being made and on the client side the hub is not tripping any disconnect - so it appears still connected on the client. I checked the hub destructor and appears to never be called.

The following is how the hub is prepared (on server):

 public void ConfigureServices(IServiceCollection services)
 {
     services.AddGrpc();
     
     services.AddCors(o =>
     {
         o.AddDefaultPolicy(b =>
         {
             b.WithOrigins(TargetURL)
                 .AllowCredentials()
                 .AllowAnyHeader();
         });
     });

     services.AddHostedService<Worker>();

     services.AddSignalR(hubOptions =>
     {
         hubOptions.EnableDetailedErrors = true;
         hubOptions.ClientTimeoutInterval = TimeSpan.FromSeconds(100);
         hubOptions.KeepAliveInterval = TimeSpan.FromMinutes(100);
     });
 }

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseSwagger();
        app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "oiStockPusherSite v1"));
    }

    app.UseRouting();
    app.UseCors(PolicyName);

    app.UseEndpoints(endpoints =>
    {
        //endpoints.MapControllers(); 
        endpoints.MapHub<TradingServiceHub>("/orderITSignalR", options =>
        {
            options.Transports = HttpTransportType.LongPolling; // you may also need this
        });
    });            
}

The following is how the HUB is activated:

protected virtual bool PrepareTradeActivityHub(string TargetHub)
{            
    string TradeActivitySignalRURL = "";
    bool Res = false;

    lock (PreparingHub)
    {
        TradeActivitySignalRURL = Connection;

        if (!PrepareNewHub)
        {                   
            if (HubConnectionOld == null)
            {                        
                EventLog.WriteEntry("Support", string.Format("Prepare Trade Service Hub for {0}", TradeActivitySignalRURL), EventLogEntryType.Information);

                HubConnectionOld = new Microsoft.AspNet.SignalR.Client.HubConnection(TradeActivitySignalRURL);
                HubConnectionOld.Closed += TradingClientHubConnection_Closed;

                ClientHubProxy = HubConnectionOld.CreateHubProxy(TargetHub);

                Task<bool> Result = TradingClientHubConnection_Connect();
                Result.Wait();

                if (!Result.Result)
                {
                    EventLog.WriteEntry("Support", "Failed to start Trade Service Hub", EventLogEntryType.Error);

                    StopHubConnection();
                }
                else
                {
                    EventLog.WriteEntry("Support", "Connected to Trade Service Hub", EventLogEntryType.Error);
                    
                    Result = ClientHubProxy?.Invoke<bool>("Active");
                    if (Result != null)
                    {
                        Result.Wait();
                        Res = Result.Result;
                        CallCheckActivated();                                
                        HubConnected = true;
                    }
                    else
                        Res = false;
                }
            }
            else
            {
                EventLog.WriteEntry("Support", "Check if Hub still connectedHub", EventLogEntryType.Information);

                Res = true;
                if (ClientHubProxy != null)
                {
                    Task.Run(async () =>
                    {
                        if (ClientHubProxy != null)
                        {
                            EventLog.WriteEntry("Support", "Test calling Hub Active operation", EventLogEntryType.Information);

                            Res = await ClientHubProxy?.Invoke<bool>("Active");

                            if (Res)
                            {                                        
                                CallCheckActivated();
                                EventLog.WriteEntry("Support", "Successfully called Hub Active operation", EventLogEntryType.Information);
                            }
                            else
                            {
                                EventLog.WriteEntry("Support", "Problem calling Hub Active operation", EventLogEntryType.Error);
                            }
                        }
                    }
                    ).Wait();
                }
                else
                {
                    EventLog.WriteEntry("Support", "Hub not avaible - should be", EventLogEntryType.Error);
                }
            }
        }
        else
        {                    
            if (HubConnectionNew == null)
            {
                EventLog.WriteEntry("Support", string.Format("Prepare Trade Service Hub for {0}", TradeActivitySignalRURL), EventLogEntryType.Information);

                HubConnectionNew = new HubConnectionBuilder().WithUrl(TradeActivitySignalRURL).Build();
                HubConnectionNew.Closed += StockPusherHubDisconnect;
               var Connect = StockPusherHubConnection_Connect();
                Connect.Wait();

                EventLog.WriteEntry("Support", "Test calling Hub Active operation", EventLogEntryType.Information);

                var Response = HubConnectionNew.InvokeAsync<bool>("Active").Result;
                if (!Response)
                {
                    HubConnectionNew = null;
                    EventLog.WriteEntry("Support", "Problem calling Hub Active operation", EventLogEntryType.Error);
                    HubConnected = false;
                }
                else
                {
                    EventLog.WriteEntry("Support", "Successfully called Hub Active operation", EventLogEntryType.Information);
                    Res = true;
                    HubConnected = true;
                    HubConnectionNew.ServerTimeout = TimeSpan.FromSeconds(100000);
                    
                }                        
            }
            else
            {
                EventLog.WriteEntry("Support", "Test calling Hub Active operation", EventLogEntryType.Information);

                var Response = HubConnectionNew.InvokeAsync<bool>("Active").Result;
                if (!Response)
                {
                    HubConnectionNew = null;
                    EventLog.WriteEntry("Support", "Problem calling Hub Active operation", EventLogEntryType.Error);
                    HubConnected = false;
                }
                else
                {
                    EventLog.WriteEntry("Support", "Successfully called Hub Active operation", EventLogEntryType.Information);
                    Res = true;
                    HubConnected = true;
                }
            }
        }
    }
    return Res;
}

protected virtual async Task StockPusherHubConnection_Connect()
{
    Exception exception = null;
    Exception except = null;

    try
    {
        if (HubConnectionNew != null)
        {
            lock (PreparingHub)
            {
                EventLog.WriteEntry("Support", string.Format("Attempt to connect to Hub for {0}", Connection), EventLogEntryType.Information);

                var HubConnect = HubConnectionNew?.StartAsync();
                HubConnect?.Wait();
                HubConnect?.ContinueWith(
                    task =>
                    {
                        try
                        {
                            if (task != null && task.IsFaulted)
                            {
                                EventLog.WriteEntry("Support", "Problem connecting to Hub for", EventLogEntryType.Error);
                                EventLog.WriteEntry("Support", task.Exception.Message, EventLogEntryType.Error);

                                exception = task?.Exception;
                                except = exception?.InnerException;
                                while (except != null)
                                {
                                    except = except?.InnerException;
                                    if (except != null)
                                        EventLog.WriteEntry("Support", except.Message, EventLogEntryType.Error);
                                }
                            }
                        }
                        catch { }
                    }
                ).Wait();
            }
        }

    }
    catch (Exception ex)
    {
    }
}

As an addition. I put together a test app that demoed this observation. It is available on https://github.com/FutureInfinite/SignalRTest

There is an operation to call back to the clients in the app called PostMessage

This operation cannot be called as the HUB appears to always be disposed. If you could try it out and see if you can determine why the hub is not active for longer periods would be great. The WPF application has a button that needs to be selected to activate the Hub.

thx Peter


Solution

  • In SignalR, the Hub class is transient. Each time a client connects or calls a Hub method, a new Hub instance is created and disposed after the request completes. Therefore, you should not start long-running tasks in the Hub constructor, as the Hub instance may be disposed before the task finishes, causing "already disposed" errors.

    To make it easier for you to test, I will provide the full test code again, as follows, everything works well. You can run it directly.

    enter image description here

    Add new Background service, here is the project structure.

    enter image description here

    1.OrderITHub.cs

    using Microsoft.AspNetCore.SignalR.Client;
    using Prism.Mvvm;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    
    namespace TestClient
    {
        public class OrderITHubProxy : BindableBase
        {
            #region "Properties & Attributes"                
            protected Microsoft.AspNetCore.SignalR.Client.HubConnection ClientHubConnectionNew;
            protected static Microsoft.AspNetCore.SignalR.Client.HubConnection ClientHubConnectionNewSinlge;
            protected bool UseSingle { get; set; } = false;
    
            protected Microsoft.AspNetCore.SignalR.Client.HubConnection HubConnectionNew
            {
                get
                {
                    if (UseSingle == null)
                        return ClientHubConnectionNew;
                    else
                        return ClientHubConnectionNewSinlge;
                }
                set
                {
                    //the use of a single hub connection requires
                    //that the hub connecion is to the same HUB
                    if (UseSingle == null)
                        ClientHubConnectionNew = value;
                    else
                        ClientHubConnectionNewSinlge = value;
                }
            }
    
            //protected static Microsoft.AspNet.SignalR.Client.IHubProxy ClientHubProxy { get; set; } = null;
            protected static object PreparingHub = new object();
            protected bool Continue { get; set; } = true;
            protected bool ActivatingTradeInterface = false;
            protected static bool HubConnected = false;
            protected string Connection;
    
            public delegate void ClientHubActive();
            public event ClientHubActive ClientHubActiveated;
    
            private static System.Timers.Timer HubConnectionTimer;
            private static object WaitToActivateHub = new object();
            #endregion //"Properties & Attributes"
    
            #region "Lifetime"
            public OrderITHubProxy(
                string Connection,
                bool UseSingle = false
                )
            {
                System.Diagnostics.EventLog.WriteEntry("Support", string.Format("Start Hub connection for {0}", Connection), System.Diagnostics.EventLogEntryType.Information);
    
                HubConnectionTimer = new System.Timers.Timer();
    
                this.Connection = Connection;
                this.UseSingle = UseSingle;
    
                ///simple thread logic to start up 
                ///and maintain the connection
                ///to the trading interface
                //HubConnectionTimer.Elapsed += PrepareTradeActivityClient;            
                //HubConnectionTimer.Start();
                PrepareTradeActivityClient(null, null);
            }
    
            private void TestApp()
            {
    
            }
    
            ~OrderITHubProxy()
            {
                StopTradeClientInterface();
            }
            #endregion "Lifetime"
    
            #region "Operations"
    
            virtual protected void PrepareTradeActivityClient(object sender, System.Timers.ElapsedEventArgs e)
            {
                Task.Factory.StartNew(
                       async () =>
                       {
                           while (Continue)
                           {
                               try
                               {
                                   ActivateTradeClientInterface();
                               }
                               finally
                               {
                                   ///5sec interval for trading interface refresh
                                   Thread.Sleep(5000);
                               }
                           }
                       }
                       , TaskCreationOptions.LongRunning
                   );
    
                //HubConnectionTimer.Interval = 5000;            
            }
    
            protected virtual bool PrepareTradeActivityHub()
            {
                string TradeActivitySignalRURL = "";
                bool Res = false;
    
                lock (PreparingHub)
                {
                    TradeActivitySignalRURL = Connection;
    
                    if (HubConnectionNew == null)
                    {
                        System.Diagnostics.EventLog.WriteEntry("Support", string.Format("Prepare Trade Service Hub for {0}", TradeActivitySignalRURL), System.Diagnostics.EventLogEntryType.Information);
    
    
                        HubConnectionNew = new Microsoft.AspNetCore.SignalR.Client.HubConnectionBuilder().WithUrl(TradeActivitySignalRURL).Build();
                        HubConnectionNew.Closed += StockPusherHubDisconnect;
                        var Connect = StockPusherHubConnection_Connect();
                        Connect.Wait();
    
                        System.Diagnostics.EventLog.WriteEntry("Support", "Test calling Hub Active operation", System.Diagnostics.EventLogEntryType.Information);
    
                        var Response = HubConnectionNew.InvokeAsync<bool>("Active").Result;
                        if (!Response)
                        {
                            HubConnectionNew = null;
                            System.Diagnostics.EventLog.WriteEntry("Support", "Problem calling Hub Active operation", System.Diagnostics.EventLogEntryType.Error);
                            HubConnected = false;
                        }
                        else
                        {
                            System.Diagnostics.EventLog.WriteEntry("Support", "Successfully called Hub Active operation", System.Diagnostics.EventLogEntryType.Information);
                            Res = true;
                            HubConnected = true;
                            HubConnectionNew.ServerTimeout = TimeSpan.FromSeconds(100000);
    
                        }
                    }
                    else
                    {
                        System.Diagnostics.EventLog.WriteEntry("Support", "Test calling Hub Active operation", System.Diagnostics.EventLogEntryType.Information);
    
                        var Response = HubConnectionNew.InvokeAsync<bool>("Active").Result;
                        if (!Response)
                        {
                            HubConnectionNew = null;
                            System.Diagnostics.EventLog.WriteEntry("Support", "Problem calling Hub Active operation", System.Diagnostics.EventLogEntryType.Error);
                            HubConnected = false;
                        }
                        else
                        {
                            System.Diagnostics.EventLog.WriteEntry("Support", "Successfully called Hub Active operation", System.Diagnostics.EventLogEntryType.Information);
                            Res = true;
                            HubConnected = true;
                        }
                    }
                }
                return Res;
            }
    
            public virtual void TradingClientHubConnection_Closed()
            {
                StopHubConnection();
            }
    
            protected virtual void StopHubConnection()
            {
                try
                {
                    if (HubConnectionNew != null && HubConnectionNew.State == HubConnectionState.Connected)
                    {
                        //HubConnectionNew.StopAsync().Wait();
                        lock (PreparingHub)
                        {
                            HubConnectionNew = null;
                        }
                    }
                }
                catch { }
                finally
                {
                    HubConnected = false;
                }
            }
    
            private void StopTradeClientInterface()
            {
                try
                {
                    StopHubConnection();
                }
                finally { }
    
            }
    
            /// <summary>
            /// activate trading services        
            /// </summary>
            protected virtual bool ActivateTradeClientInterface()
            {
                bool bRes = false;
    
                if (!ActivatingTradeInterface)
                {
                    try
                    {
                        ActivatingTradeInterface = true;
    
                        System.Diagnostics.EventLog.WriteEntry("oiTrader Client", "Attempt to connect to Trader Hub", System.Diagnostics.EventLogEntryType.Information);
    
                        if (PrepareTradeActivityHub())
                        {
                            bRes = true;
                            Action TestRequestResult = TestApp;
                            HubConnectionNew.On("TestOp", TestRequestResult);
                            //For test
                            HubConnectionNew.On("TestOp", () =>
                            {
                                MessageBox.Show("Receive a TestOp message from the server");
                            });
    
                        }
                        else
                            StopHubConnection();
                    }
                    catch (Exception ex)
                    {
                        StopHubConnection();
                    }
                    finally { ActivatingTradeInterface = false; }
                }
                else
                    bRes = HubConnected;
    
                return bRes;
            }
    
            protected virtual Task StockPusherHubDisconnect(Exception arg)
            {
                StockPusherHubConnection_Connect().Wait();
    
                return null;
            }
    
            protected virtual async Task StockPusherHubConnection_Connect()
            {
                Exception exception = null;
                Exception except = null;
    
                try
                {
                    if (HubConnectionNew != null)
                    {
                        lock (PreparingHub)
                        {
                            System.Diagnostics.EventLog.WriteEntry("Support", string.Format("Attempt to connect to Hub for {0}", Connection), System.Diagnostics.EventLogEntryType.Information);
    
                            var HubConnect = HubConnectionNew?.StartAsync();
                            HubConnect?.Wait();
                            HubConnect?.ContinueWith(
                                task =>
                                {
                                    try
                                    {
                                        if (task != null && task.IsFaulted)
                                        {
                                            System.Diagnostics.EventLog.WriteEntry("Support", "Problem connecting to Hub for", System.Diagnostics.EventLogEntryType.Error);
                                            System.Diagnostics.EventLog.WriteEntry("Support", task.Exception.Message, System.Diagnostics.EventLogEntryType.Error);
    
                                            exception = task?.Exception;
                                            except = exception?.InnerException;
                                            while (except != null)
                                            {
                                                except = except?.InnerException;
                                                if (except != null)
                                                    System.Diagnostics.EventLog.WriteEntry("Support", except.Message, System.Diagnostics.EventLogEntryType.Error);
                                            }
                                        }
                                    }
                                    catch { }
                                }
                            ).Wait();
                        }
                    }
    
                }
                catch (Exception ex)
                {
                }
            }
    
            protected virtual void StopStockPusher()
            {
                try
                {
                    if (HubConnectionNew != null && HubConnectionNew.State == HubConnectionState.Connected)
                        HubConnectionNew.StopAsync();
                }
                catch { }
    
                HubConnectionNew = null;
            }
    
            protected void CallCheckActivated()
            {
                if (ClientHubActiveated != null) ClientHubActiveated();
            }
            #endregion //"Operations"
        }
    }
    

    2.MessageBackgroundService.cs

    using Microsoft.AspNetCore.SignalR;
    
    namespace TestClientServer
    {
        public class MessageBackgroundService : BackgroundService
        {
            private readonly IHubContext<TheHub> _hubContext;
    
            public MessageBackgroundService(IHubContext<TheHub> hubContext)
            {
                _hubContext = hubContext;
            }
    
            protected override async Task ExecuteAsync(CancellationToken stoppingToken)
            {
                while (!stoppingToken.IsCancellationRequested)
                {
                    await Task.Delay(1000, stoppingToken);
    
                    await _hubContext.Clients.All.SendAsync("TestOp", cancellationToken: stoppingToken);
                }
            }
        }
    }
    

    3.Startup.cs

    using Microsoft.AspNetCore.Http.Connections;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace TestClientServer
    {                  
        internal class Startup
        {
            #region Properties&Attributes
            public static string ListenPort { get; private set; }
            public static string TargetURL { get; private set; }
            private static string PolicyName { get { return "Trading Client Policy"; } }
            #endregion Properties&Attributes
    
            #region Lifetime
            static Startup()
            {
    #if DEBUG
                ListenPort = "20";
                Console.WriteLine(string.Format("HUB URL {0}", ListenPort));
    #else
                ListenPort = System.Configuration.ConfigurationManager.AppSettings["TradeClientSignalRURL"];
    #endif
    
                TargetURL = string.Format("http://localhost:{0}", ListenPort);
                Console.WriteLine(string.Format("orderIT HUB listenining on {0}", TargetURL));
            }
    
            #endregion Lifetime
    
            #region Operations   
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddGrpc();
    
                services.AddCors(o =>
                {
                    o.AddDefaultPolicy(b =>
                    {
                        b.WithOrigins(TargetURL)
                            .AllowCredentials()
                            .AllowAnyHeader();
                    });
                });
    
                //services.AddHostedService<Worker>();
    
                services.AddSignalR(hubOptions =>
                {
                    //hubOptions.EnableDetailedErrors = true;
                    hubOptions.ClientTimeoutInterval = TimeSpan.FromSeconds(100);
                    //hubOptions.KeepAliveInterval = TimeSpan.FromMinutes(1);
                });
                // Register MessageBackgroundService
                services.AddHostedService<MessageBackgroundService>();
            }
    
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                    //app.UseSwagger();
                    //app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "oiStockPusherSite v1"));
                }
    
                app.UseRouting();
                app.UseCors(PolicyName);
    
                app.UseEndpoints(endpoints =>
                {
                    //endpoints.MapControllers(); 
                    endpoints.MapHub<TheHub>("/TestHub", options =>
                    {
                        options.Transports = HttpTransportType.LongPolling; // you may also need this
                    });
                });
            }
    
            #endregion Operations
        }
    }
    

    4.TheHub

    using Microsoft.AspNetCore.SignalR;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Timers;
    
    namespace TestClientServer
    {
        public class TheHub : Microsoft.AspNetCore.SignalR.Hub
        {
            bool IsDisposed = true;
            private Task CallTask;
            public TheHub()
            {  
                // Remove the code       
            }
    
            protected override void Dispose(bool disposing)
            {
                IsDisposed = true;
                base.Dispose(disposing);
            }
    
            public override Task OnConnectedAsync()
            {
                IsDisposed = false;
                return base.OnConnectedAsync();
            }
    
            public void PostMessage(object sender, ElapsedEventArgs e)
            {
                if (!IsDisposed)
                    Clients?.All.SendAsync("TestOp");                                                                        
            }
    
            public bool Active() => true;
        }
    }