Search code examples
c#signalr

"Cannot access a disposed object" crash in SignalR


I have a test hub with a timer that sends the date to all clients.

Once a client connects, it crashes with the following error: Cannot access a disposed object.

Here is the error:

System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'MyHub'.
   at Microsoft.AspNetCore.SignalR.Hub.CheckDisposed()
   at Microsoft.AspNetCore.SignalR.Hub.get_Clients()

Here is the hub code:

public class MyHub : Hub
{
    public MyHub()
    {
        Program.T = new Timer(TickTimer, null, 1000, 1000);
    }

    private void TickTimer(object State)
    {
        try
        {
            var Time = DateTime.UtcNow.ToString(CultureInfo.InvariantCulture);
            Console.WriteLine(Time);

            Clients.All.SendCoreAsync("update", new object[] { Time });
        }
        catch (Exception E)
        {
            Console.WriteLine(E);
            throw;
        }
    }
}

It looks like the Clients object has been disposed of, but I don't understand why.


Edit, here is more information:

The hubs can come from different assemblies, so they are registered dynamically, in the configure section of the asp startup.

Each hub gets decorated with an attribute to identify it and provide a path:

[AttributeUsage(AttributeTargets.Class)]
public class SignalRHub : Attribute
{
    public readonly string Route;

    public SignalRHubPath(string Route)
    {
        this.Route = Route;
    }
}

And then they are found and registered this way:

    private static void RegisterHubs(IApplicationBuilder Application)
    {
        // find all SignalR hubs
        var HubsList = ReflectionHelper.FindType<SignalRHubPath>();
        Logging.Info($"Found {HubsList.Count} hubs");

        // get a link to the mapper method of the hubroutebuilder.
        var MapperMethodInfo = typeof(HubRouteBuilder).GetMethod("MapHub", new[] { typeof(PathString) }, null);

        // register them
        foreach (var H in HubsList)
        {
            // get the route attribute
            var Route = string.Empty;
            var Attributes = Attribute.GetCustomAttributes(H);
            foreach (var Attribute in Attributes)
            {
                if (Attribute is SignalRHubPath A) { Route = A.Route; break; }
            }

            // register the hub
            if (string.IsNullOrEmpty(Route))
            {
                Logging.Warn($"[Hub] {H.Name} does not have a path, skipping");
            }
            else
            {
                Logging.Info($"[Hub] Registering {H.Name} with path {Route}");
                // Application.UseSignalR(_ => _.MapHub<Hub>("/" + Route));
                // use the mapper method call instead so we can pass the hub type
                var Path = new PathString("/" + Route);
                Application.UseSignalR(R => MapperMethodInfo.MakeGenericMethod(H).Invoke(R, new object [] { Path }));
            }
        }
    }

Solution

  • Hub lifetime is per request (see Note at https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-3.1 ) so you get disposed exception because you are accessing a property (Clients) of a disposed object.

    When you want to send a message to clients outside a Hub (and you are outside, since reacting to a timer, thus after the .netcore hub lifetime) you should work with a IHubContext (which you can get by DI), have a look at https://learn.microsoft.com/en-us/aspnet/core/signalr/hubcontext?view=aspnetcore-3.1