Search code examples
asp.net-coretimersignalr-hub

Using UserManager not working inside Timer


In my project I am trying to get a user based on it's email adress every second with the UserManager but when I do this I get the following error Cannot access a disposed object Object name: 'UserManager1, but this is when I do it inside of a Timer(). If I just do it once there is no problem, how can I fix this? This timer is inside a class that is being called by a SignalR Hub.

Code:

Timer = new System.Threading.Timer(async (e) =>
{
   IEnumerable<Conversation> conversations = await _conversationsRepo.GetAllConversationsForUserEmailAsync(userMail);
   List<TwilioConversation> twilioConversations = new List<TwilioConversation>();

   foreach (Conversation conversation in conversations)
   {
     TwilioConversation twilioConversation = await _twilioService.GetConversation(conversation.TwilioConversationID);
     twilioConversation.Messages = await _twilioService.GetMessagesForConversationAsync(conversation.TwilioConversationID);

     twilioConversation.ParticipantNames = new List<string>();
     List<TwilioParticipant> participants = await _twilioService.GetParticipantsForConversationAsync(conversation.TwilioConversationID);
     foreach (TwilioParticipant participant in participants)
     {
       User user = await _userManager.FindByEmailAsync(participant.Email);
       twilioConversation.ParticipantNames.Add(user.Name);
     }

     twilioConversations.Add(twilioConversation);
    }
 }, null, startTimeSpan, periodTimeSpan);

Solution

  • UserManager along with quite a few other types is a service that has a scoped lifetime. This means that they are only valid within the lifetime of a single request.

    That also means that holding on to an instance for longer is not a safe thing to do. In this particular example, UserManager depends on the UserStore which has a dependency on a database connection – and those will definitely be closed when the request has been completed.

    If you need to run something outside of the context of a request, for example in a background thread, or in your case in some timed execution, then you should create a service scope yourself and retrieve a fresh instance of the dependency you rely on.

    To do that, inject a IServiceScopeFactory and then use that to create the scope within your timer code. This also applies to all other scoped dependencies, e.g. your repository which likely requires a database connection as well:

    Timer = new System.Threading.Timer(async (e) =>
    {
        using (var scope = serviceScopeFactory.CreateScope())
        {
            var conversationsRepo = scope.ServiceProvider.GetService<ConversionsRepository>();
            var userManager = scope.ServiceProvider.GetService<UserManager<User>>();
    
            // do stuff
        }
    }, null, startTimeSpan, periodTimeSpan);