【问题标题】:.NET Core DI - disposing of Singleton service in Console App.NET Core DI - 在控制台应用程序中处理单例服务
【发布时间】:2021-03-15 18:17:33
【问题描述】:

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

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

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

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()让一切尘埃落定。 显然这还不够,因为我的SampleSingletonService 中的Dispose() 从未被调用过(ISampleSingletonService 继承自IDisposable)。

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

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

我尝试以某种方式处理/清理/关闭根IServiceProvider,但它没有从IDisposable 继承,而且我似乎没有一个可用的方法来清理它造成的混乱。

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

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

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

【问题讨论】:

  • @apocalypse 我见过,但问题是他们使用 HostBuilder 以某种方式管理 ServiceProvider 的生命周期。我自己创建了 ServiceProvider(使用 BuildServiceProvider 方法),所以这个场景对我来说效果不太好。
  • 主机生成器专为此类场景而设计。最好使用它来创建自定义逻辑。

标签: c# .net-core dependency-injection singleton console-application


【解决方案1】:

您需要在 _services 字段上调用 ​​Dispose。 释放作用域时,只会释放作用域服务,不会释放单例。

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

【讨论】:

  • 确实如此,但问题是,_services 字段是 IServiceProvider,它不包含 IDisposable。这就是让我失望的原因。不过,谢谢。
【解决方案2】:

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

所以将我的字段更改为:

static ServiceProvider _services;

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

文档链接: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 之前它是相同的。

【讨论】:

    猜你喜欢
    • 2017-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-23
    • 1970-01-01
    相关资源
    最近更新 更多