3

I've got a problem in my app where two IHostedService instances are started (StartAsync()) in the wrong order. One takes a dependency on the other (but not in the constructor, but later in another class through a IServiceScopeFactory).

Let's say there are services A and B.

Service B needs service A, so it will (somewhere down the class hierarchy) get an instance of service A and call a method on it. But service A can only be used after it has been started. Now the ASP.NET Core (2.2) app starts service B first, then service A. That leads to several errors in the code logic because use-before-start is bad and start-after-use is also bad.

Service A needs a database connection to initialise its state, which I assume is only available in the StartAsync method, not already in the constructor.

Here's the code skeleton:

class ServiceA : IHostedService
{
    public Task StartAsync(CancellationToken cancellationToken)
    {
        // Load working state from the database.
        // This may take a moment depending on the amount of data to read.
    }

    public void DoSomething(object data)
    {
        // Use the working state and decide whether to add a database record.
        // Will fail if the database record already exists but the working
        // state doesn't reflect that yet because it wasn't loaded.
        // This method should not be async.
    }
}

class ServiceB : IHostedService
{
    public Task StartAsync(CancellationToken cancellationToken)
    {
        // Start other code which will eventually call
        // ServiceA.DoSomething before it was fully started.
    }
}

// ASP.NET Core Startup class
class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // The order doesn't matter here
        services.AddSingletonHostedService<ServiceA>();
        services.AddSingletonHostedService<ServiceB>();
    }
}

// Extension method
public static IServiceCollection AddSingletonHostedService<TService>(this IServiceCollection services)
    where TService : class, IHostedService
{
    services.AddSingleton<TService>();
    services.AddSingleton<IHostedService, BackgroundServiceStarter<TService>>();
    return services;
}

For BackgroundServiceStarter see https://stackoverflow.com/a/51584615. This is required to make IHostedService services available through dependency injection which wasn't thought of by Microsoft.

How can I tell the dependency container that it should start service A before starting service B (and wait for it)?

If that isn't possible, I'll have to let uses of service A before it has been started block until is has been started. I'm not sure if this will cause problems with a potentially large number of blocked threads (the API of service A is not async and doesn't have to be). There should be no circular dependency so it shouldn't dead-lock, but you never know what will be added later.

ygoe
  • 18,655
  • 23
  • 113
  • 210
  • This is a design issue. `IHostedService` services should be independent of each other. Show some code to help clarify the issue. – Nkosi Nov 15 '19 at 18:10
  • The code wouldn't add any meaning. I could show you empty classes with the described dependencies but you can imagine them yourself just as well. Not sure what you want to see. – ygoe Nov 15 '19 at 18:13
  • You specifically mentioned `IHostedService` so that is why I asked to see how they were implemented. They are framework specific and may have been implemented incorrectly. – Nkosi Nov 15 '19 at 18:14
  • I've added some code. Hope this helps. – ygoe Nov 15 '19 at 18:36

0 Answers0