简体   繁体   中英

.NET Core DI - disposing of Singleton service in Console App

I've got this small console app for which I decided to wire up Microsoft's DI so it will care of dependencies for me. I don't have that much of a scopes, but rather transient and singleton services.

The problem is, it doesn't dispose singleton services. Maybe it's because it doesn't know when I'm done with it? Maybe because of some other reason.

Here's code of my static class handling injection (obviously in final app it's a bit more complex)

public static class ServiceInjector
{
    static IServiceProvider _services;

    static Stack<IServiceScope> _scopeStack;

    static IServiceProvider _provider => _scopeStack.NullPeek()?.ServiceProvider ?? _services;

    public static void Configure()
    {
        var svcCollection = new ServiceCollection();
        svcCollection.AddSingleton<ISampleSingletonService, SampleSingletonService>();

        _services = svcCollection.BuildServiceProvider();
        _scopeStack = new Stack<IServiceScope>();
    }

    public static T GetService<T>()
    {
        return _provider.GetService<T>();
    }

    public static void StartScope()
    {
        _scopeStack.Push(_services.CreateScope());
    }

    public static void KillAllScopes()
    {
        while(_scopeStack.TryPop(out IServiceScope scope))
        {
            scope.Dispose();
        }

        _services = null; // I also tried to convince GC to clean it up and force Dispose. Nope.
    }
}

In Program.Main I call ServiceInjector.Configure() method, then ServiceInjector.StartScope() , then do stuff (injecting services) and in the end (in finally block of global exception handler) I call ServiceInjector.KillAllScopes() , when I want for it all to clear up. Obviously it's not enough because Dispose() of my SampleSingletonService is never called ( ISampleSingletonService inherits from IDisposable ).

It's a problem because I use ILoggerProvider (from another Microsoft.Extensions. package) which caches stuff and flushes it to file each few seconds. If global exception handler finds some exception, I log it and want it to be flushed to disk, then exit the app. Without Dispose being called, app closes before last message can be flushed, so I loose that message.

I could obviously just halt the app for time longer than needed to flush to make sure it gets done, but let's just say it's not perfect solution.

I tried somehow disposing/cleaning/closing root IServiceProvider , but it doesn't inherit from IDisposable and I none of the available methods seem to be cleaning up mess created by it.

I also tried setting _service to null and calling GC.Collect() , but Garbage Collector seemed to not being interested in helping me;)

For the demo purpose I created minimalistic reproduction app, which shows the issue (message from Dispose method of sample service is never logged): https://github.com/domints/DotnetDISingletonDisposingPoC

Thanks for any idea on how to resolve that problem.

You need to invoke Dispose on _services field. When disposing a scope, only scoped services will be disposed, but not singletons.

var serviceCollection = new ServiceCollection();
// configure services
using var serviceProvider = serviceCollection.BuildServiceProvider();
using var serviceScope = serviceProvider.CreateScope();

I found the solution, and the culprit. After relooking into this question, and the docs and the code I noticed svcCollection.BuildServiceProvider() doesn't return IServiceProvider , but ServiceProvider , which implements both IServiceProvider and IDisposable .

So changing my field to:

static ServiceProvider _services;

gave me access to Dispose() method and I could gracefully tell ServiceProvider I do not need him anymore so he can clean up after himself.

Link to docs: https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicecollectioncontainerbuilderextensions.buildserviceprovider?view=dotnet-plat-ext-5.0#Microsoft_Extensions_DependencyInjection_ServiceCollectionContainerBuilderExtensions_BuildServiceProvider_Microsoft_Extensions_DependencyInjection_IServiceCollection_System_Boolean _

Just as a note, it works the same way in .NET Core 3.1 and 5.0, can't assure for others, but I believe it would be the same for at least up to 2.2.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM