.NET Core DI - 在控制台应用程序中处理单例服务

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

我有这个小型控制台应用程序,我决定为其连接 Microsoft 的 DI,以便它为我处理依赖项。我没有那么多范围,而是瞬态和单例服务。

问题是,它不处理单例服务。也许是因为它不知道我什么时候完成它?可能是其他原因吧。

这是我的静态 class 处理注入的代码(显然在最终应用程序中它有点复杂)

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.
    }
}

Program.Main 中我调用 ServiceInjector.Configure() 方法,然后是 ServiceInjector.StartScope(),然后做一些事情(注入服务),最后(在全局异常处理程序的 finally 块中) 当我希望一切都清理干净时,我会调用 ServiceInjector.KillAllScopes()。 显然这还不够,因为我的 SampleSingletonServiceDispose() 从未被调用(ISampleSingletonService 继承自 IDisposable)。

这是一个问题,因为我使用 ILoggerProvider(来自另一个 Microsoft.Extensions. 包),它缓存内容并每隔几秒将其刷新到文件中。如果全局异常处理程序发现一些异常,我会记录它并希望将其刷新到磁盘,然后退出应用程序。如果没有调用 Dispose,应用程序会在最后一条消息被刷新之前关闭,所以我丢失了那条消息。

我显然可以暂停应用程序的时间比刷新所需的时间更长,以确保它完成,但我们只能说这不是完美的解决方案。

我以某种方式尝试过 disposing/cleaning/closing root IServiceProvider,但它不是从 IDisposable 继承的,我 none 的可用方法似乎正在清理创建的混乱通过它。

我也试过将 _service 设置为 null 并调用 GC.Collect(),但垃圾收集器似乎没有兴趣帮助我 ;)

出于演示目的,我创建了简约的复制应用程序,它显示了问题(从未记录来自 Dispose 示例服务方法的消息):https://github.com/domints/DotnetDISingletonDisposingPoC

感谢您提供有关如何解决该问题的任何想法。

我找到了解决方案和罪魁祸首。在重新研究这个问题之后,我注意到 svcCollection.BuildServiceProvider() 的文档和代码不是 return IServiceProvider,而是 ServiceProvider,它实现了 IServiceProviderIDisposable.

因此将我的字段更改为:

static ServiceProvider _services;

让我可以访问 Dispose() 方法,我可以优雅地告诉 ServiceProvider 我不再需要他了,这样他就可以自己清理了。

Link 到文档: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_

请注意,它在 .NET Core 3.1 和 5.0 中的工作方式相同,无法向其他人保证,但我相信它至少在 2.2 之前是相同的。

您需要在 _services 字段上调用 ​​Dispose。 销毁作用域时,只会销毁范围内的服务,不会销毁单例。

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