Search code examples
c#asp.net-corebackgroundworkerasp.net-core-hosted-services

Running a long task IHosted service at the same time and can manually turned on and off


hi i am having a problem in running a long task for my background service using the IHostedService at first it really work fine but in the long run the background service suddenly stopped with this thread exited code:

The thread 10824 has exited with code 0 (0x0).
The thread 12340 has exited with code 0 (0x0).
The thread 9324 has exited with code 0 (0x0).
The thread 11168 has exited with code 0 (0x0).
The thread 11616 has exited with code 0 (0x0).
The thread 9792 has exited with code 0 (0x0).

i register my background service as

//Register Background 
serviceCollection.AddSingleton<CoinPairBackgroundService>
serviceCollection.AddSingleton<SaveFakePersonBackgroundService>();
serviceCollection.AddSingleton<LeaderboardMinutesBackgroundService>();
serviceCollection.AddSingleton<LeaderboardHoursBackgroundService>();

serviceCollection.AddSingleton<IHostedService, CoinPairBackgroundService>();
serviceCollection.AddSingleton<IHostedService, SaveFakePersonBackgroundService>();
serviceCollection.AddSingleton<IHostedService, LeaderboardMinutesBackgroundService>();
serviceCollection.AddSingleton<IHostedService, LeaderboardHoursBackgroundService>();

because in the future i want to manually turned on and off my background services with use of

IServiceProvider provider = _serviceProvider.GetService<MyBackgroundServiceHere>();
provider.StartAsync();
provider.StopAsync

and this was my code in my background services StartAsync

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogDebug("Leaderboard minute ranking updates is starting");
        Task.Run(async () =>
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                _logger.LogDebug("Leaderboard minute ranking updates  is dequeueing");
                await DequeueRandomCustomers();

                _logger.LogDebug("Leaderboard minute ranking updates  is enqueueing");
                await EnqueueRandomCustomers();

                _logger.LogDebug("Leaderboard minute ranking updates  thread is now sleeping");
                //sleep for 1 minute
                await Task.Delay(new TimeSpan(0, 0, 10));
            }
        });
        return Task.CompletedTask;
    }

I am confuse if there are some problems with registering my background service because i see my background service starts but in the long run it suddenly stopped i already get rid of those Thread.Sleep() in my background services hope you can help thanks in advance.


Solution

  • Instead of managing hosted services lifetime in a such weird way, you can set configuration for services inside appsettings.json, configure it as reloadOnChange: true, and use IOptionsMonitor<> to access current values from appsettings.json. And use that settings as a cancellation for started task inside hosted service's StartAsync method. And you need to listen to OnChange event of IOptionsMonitor<> for restarting services.

    appsettings.json:

     "HostedService": {
        "RunService1": true
      }
    

    Register that options so:

    public class HostedServiceConfig
    {
         public bool RunService1 { get; set; }
    }
    
    services.Configure<HostedServiceConfig>(Configuration.GetSection("HostedService"));
    

    And then create HostedService1 and register with the help of the AddHostedService:

     services.AddHostedService<HostedService1>();
    

    And here is the example for StartAsync:

      public class HostedService1 : IHostedService
      {
            private readonly IOptionsMonitor<HostedServiceConfig> _options;
            private CancellationToken _cancellationToken;
    
            public HostedService1(IOptionsMonitor<HostedServiceConfig> options)
            {
                _options = options;
    
                 // Or listen to changes and re-run all services from one place inside `Configure` method of `Startup`
                _options.OnChange(async o =>
                {
                    if (o.RunService1)
                    {
                        await StartAsync(_cancellationToken);
                    }
                });
            }
    
            public Task StartAsync(CancellationToken cancellationToken)
            {
                if(_cancellationToken == null) 
                {
                     _cancellationToken = cancellationToken;
                }
    
                Task.Run(async () =>
                {
                    while (!_cancellationToken .IsCancellationRequested || !_options.CurrentValue.RunService1)
                    {
                         ....
                    }
                });
    
                return Task.CompletedTask;
            }
    
            ...
       }
    

    In case of any changes inside appSettings.json, you _options.CurrentValue.RunService1 will be reloaded automatically.