c# - 为什么 GetService 创建的对象不会被破坏?

标签 c# dependency-injection memory-leaks microsoft.extensions.hosting

我正在编写一个针对 dotnet core 框架 3.1 的应用程序。我使用依赖注入(inject)来配置数据库上下文等。在我的 Program.cs 中,我有以下代码:

var host = new HostBuilder()
    .ConfigureHostConfiguration(cfgHost =>
    {
        ...
    })
    .ConfigureAppConfiguration((hostContext, configApp) =>
    {
        ....
    })
    .ConfigureServices((hostContext, services) =>
    {
        ...
        services.AddDbContext<MyHomeContext>(options =>
        {
            options.UseNpgsql(hostContext.Configuration.GetConnectionString("DbContext"));
        }, ServiceLifetime.Transient);
        ...
    })
    .ConfigureLogging((hostContext, logging) =>
    {
        ...    
    })
    .Build();

我将 host 传递给另一个类。在另一个类中,作为较长方法的一部分,我有以下代码:

    using (var context = Host.Services.GetService(typeof(MyHomeContext)) as MyHomeContext)
    {
        StatusValues = context.Status.ToDictionary(kvp => kvp.Name, kvp => kvp.Id);
    }
    GC.Collect();
    GC.Collect();

GC.Collect 调用用于测试/调查目的。在 MyHomeContext 中,出于测试目的,我实现了析构函数和 Dispose() 的重写。 Dispose() 被调用,但析构函数永远不会被调用。 这会导致我创建的每个 MyHomeContext 实例出现内存泄漏。

我错过了什么?我可以做什么来确保当我不再需要 MyHomeContext 实例时将其删除。

我转向这个工具有几个原因:

  • 我只需要短时间内的数据库连接。
  • 我插入了大量数据(不在上面简化的示例/测试代码中),导致 DbContext 保留大量缓存。我原以为处理该对象会释放内存,但现在我只会让情况变得更糟:(

当我用 new MyHomeContext() 替换 Host.Services.GetService(typeof(MyHomeContext)) as MyHomeContext 时,MyHomeContext 的析构函数是被召唤。在我看来,依赖注入(inject)框架中的某些东西保存着对该对象的引用。这是真的?如果是这样,我如何告诉它释放它?

最佳答案

很难对你的问题给出一个好的答案,因为有很多误解需要解决。以下是一些需要注意的事项:

  • 在调试器中运行的非优化(调试构建).NET 应用程序的行为与未附加调试器的优化应用程序有很大不同。其一,在调试时,方法的所有变量将始终保持引用状态。这意味着对 GC.Collect() 的任何调用都将无法清理由同一方法引用的 context 变量。
  • Dispose Pattern如果实现正确,则在调用其 Dispose 方法时,类将抑制对终结器的调用。这是通过调用GC.SuppressFinalize来完成的。 。 Entity Framework 的 DbContext 正确实现了 Dispose 模式,这也会导致您看不到终结器被命中。
  • 终结器(析构函数)在名为 finalizer thread 的后台线程上调用。 。这意味着,即使您的上下文已取消引用并且符合垃圾回收条件,终结器也不太可能在调用GC.Collect()之后立即调用。但是,您可以通过调用 GC.WaitForPendingFinalizers() 停止应用程序并等待调用终结器。 。调用 WaitForPendingFinalizers 几乎不是您想要在生产中执行的操作,但它对于测试和基准测试目的非常有用。

除了这些 CLR 特定部分之外,这里还有一些关于 DI 部分的反馈:

  • 从 DI 容器解析的服务不应直接处置。相反,由于 DI 容器可以控制其创建,因此您也应该让它控制其销毁。
  • 执行此操作的方法(使用 MS.DI)是创建一个 IServiceScope。服务缓存在这样的范围内,当该范围被处置时,它将确保其缓存的一次性服务也被处置,并且它将确保以与创建相反的顺序完成此操作。
  • 直接从根容器(在您的情况下是 Host.Services)请求服务是一个坏主意,因为它会导致作用域服务(例如您的 DbContext)缓存在根容器中。这使它们实际上成为单例。换句话说,无论您从 Host.Services 请求它的频率如何,相同的 DbContext 实例都将在应用程序的持续时间内重复使用。这可能会导致all sorts of hard to debug problems 。解决方案再次是创建一个范围并从该范围进行解析。例子:
    var factory = Host.Services.GetRequiredService<IServiceScopeFactory>();
    using (var scope = factory.CreateScope())
    {
        var service = scope.ServiceProvider.GetRequiredService<ISomeService>();
        service.DoYourMagic();
    }
    
  • 请注意,使用 ASP.NET Core 时,您通常不必手动创建新作用域 - 每个 Web 请求都会自动获取自己的作用域。您的所有类都会自动从某个范围请求,并且该范围会在网络请求结束时自动清理。

关于c# - 为什么 GetService 创建的对象不会被破坏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61348088/

相关文章:

java - Juice 注入(inject)器抛出空指针异常

javascript - 在tensorflow JavaScript中使用posenet时出现内存泄漏

c# - 如何将 IObjectContextAdapter 从 EF 6 适配到​​ EF Core

c#通过程序在启动下添加应用

c# - 如何在 .net core 1.0 中获取接口(interface)的所有实例

c# - 基于运行时输入的依赖注入(inject)

android - Android什么时候认为窗口泄漏了?

c++ - 列出 "cold"内存区域

c# - 是否可以在 Visual Studio 中调试时编辑代码,例如在 Eclipse(Java)中

c# - 私有(private) Azure 云服务?