Search code examples
c#taskasp.net-core-hosted-services

Access Dictionary Items within Method Override


I have the below class in a webapi which adds to the dictionary however everytime the checkstatus method is triggered (using Task.Delay) it says the dictionary is empty. Is this because its running on a different thread? Should I be using a concurrentdictionary instead?

internal class Sms : smsBase
{
    private const int CheckFrequencyMilliseconds = 1000;
    private Dictionary<CustomerDetails, decimal> _registeredCustomers;

    public Sms(ILogger<Sms> logger)
        : base(TimeSpan.FromMilliseconds(CheckFrequencyMilliseconds), logger)
    {
        _registeredCustomers = new Dictionary<CustomerDetails, decimal>();
    }

    protected override Task CheckStatus()
    {

        foreach (var rc in _registeredCustomers)
        {
         //do something
        }

        return Task.CompletedTask;
    }

    public Task RegisterCustomer(CustomerDetails customer)
    {
        _registeredCustomers.Add(customer, 1);
        return Task.CompletedTask;
    }
}

Base class code below.

public abstract class smsBase : BackgroundService
{
    private readonly TimeSpan _tickFrequency;
    private readonly ILogger<smsBase> _logger;

/// <inheritdoc />
/// <param name="tickFrequency">Frequency that the service will execute the CheckStatus method.</param>
protected smsBase (TimeSpan tickFrequency,ILogger<smsBase> logger)
{
  this._tickFrequency = tickFrequency;
  this._logger = logger;
}

/// <inheritdoc />
protected override sealed async Task ExecuteAsync(CancellationToken stoppingToken)
{
  this._logger.LogInformation("Service is starting.");
  while (!stoppingToken.IsCancellationRequested)
  {
    ConfiguredTaskAwaitable configuredTaskAwaitable;
    try
    {
      configuredTaskAwaitable = this.CheckStatus().ConfigureAwait(false);
      await configuredTaskAwaitable;
    }
    catch (Exception ex)
    {
      this._logger.LogError(ex, "An exception was thrown whilst checking registered strategies.");
      throw;
    }
    configuredTaskAwaitable = Task.Delay(this._tickFrequency, stoppingToken).ConfigureAwait(false);
    await configuredTaskAwaitable;
  }
  this._logger.LogInformation("Service is stopping.");

protected abstract Task CheckStatus();
}

And below is the startup cs

    public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services
            .AddSwaggerGen(options => {
                options.SwaggerDoc("v1", new Info() { Title = "Customers.WebApi", Version = "v1" });
            })
            .AddMvc(options => {
                options.Filters.Add(new ExceptionFilter());
                options.Filters.Add(new ProducesAttribute("application/json"));
            })
            .AddJsonOptions(options => {
                options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind;
                options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            });

        services.AddHostedService<Sms>();
        services.AddSingleton<ISms, Sms>();
    }

Solution

  • There are many points missing from your question, but I believe I can derive the problem from just these 2 lines

    services.AddHostedService<Sms>();
    services.AddSingleton<ISms, Sms>();
    

    The AddSingleton line will create a single instance of Sms for your application, and I suspect you're injecting ISms into a controller somewhere and calling RegisterCustomer.

    The AddHostedService line will create a second instance of Sms, and use it as the hosted service.

    The solution is to split these 2 things apart, and have something like ICustomerRepository which is shared by both the hosted service and the controllers.


    Note that if I'm well off the mark here, it would help to edit your question with the details of ISms, including how it is used, and where you call RegisterCustomer.