1

我正在编写一个针对 dotnet 核心框架 3.1 的应用程序。我使用依赖注入来配置数据库上下文等。在我的 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电话用于测试/调查目的。在MyHomeContextI 中,出于测试目的,实现了析构函数和 Dispose() 的覆盖。Dispose() 被调用,但析构函数永远不会被调用。这会导致MyHomeContext我创建的每个实例都发生内存泄漏。

我错过了什么?我能做些什么来确保MyHomeContext当我不再需要它的实例时被删除。

由于以下几个原因,我转向了这个工具:

  • 我只需要在短时间内建立数据库连接。
  • 我插入了大量数据(不是在上面简化的示例/测试代码中),导致 DbContext 保持很大的缓存。我希望处理对象会释放内存,但现在我只会让它变得更糟:(

当我被调用Host.Services.GetService(typeof(MyHomeContext)) as MyHomeContextnew MyHomeContext()析构函数替换时。MyHomeContext在我看来,依赖注入框架中的某些东西持有对该对象的引用。这是真的?如果是这样,我怎么能告诉它释放它?

4

1 回答 1

4

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

  • 在调试器中运行的非优化(调试构建).NET 应用程序的行为与未连接调试器的优化应用程序完全不同。一方面,在调试时,方法的所有变量将始终保持引用。这意味着任何调用GC.Collect()都无法清除context由同一方法引用的变量。
  • Dispose 模式正确实现时,调用终结器的Dispose方法将被类抑制。这是通过调用GC.SuppressFinalize来完成的。实体框架DbContext正确实现了 Dispose 模式,这也会导致您看不到终结器被命中。
  • 终结器(析构函数)在称为终结器线程的后台线程上调用。这意味着即使您context已被取消引用并且有资格进行垃圾回收,也不太可能在调用GC.Collect(). 但是,您可以暂停应用程序并等待通过调用GC.WaitForPendingFinalizers()调用终结器。调用WaitForPendingFinalizers几乎不是您想要在生产中执行的操作,但它对于测试和基准测试很有用。

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

  • 从 DI 容器解析的服务不应该被直接释放。相反,由于 DI 容器可以控制其创建,因此您也应该让它控制其销毁。
  • 执行此操作(使用 MS.DI)的方法是创建一个IServiceScope. 服务被缓存在这样的范围内,当范围被释放时,它将确保其缓存的一次性服务也被释放,并确保以相反的创建顺序完成。
  • 直接从根容器(Host.Services在您的情况下)请求服务是一个坏主意,因为它会导致作用域服务(例如您的DbContext)被缓存在根容器中。这导致它们有效地成为单例。换句话说,DbContext无论您多久从Host.Services. 这会导致各种难以调试的问题。再次,解决方案是创建一个范围并从该范围解析。例子:
    var factory = Host.Services.GetRequiredService<IServiceScopeFactory>();
    using (var scope = factory.CreateScope())
    {
        var service = scope.ServiceProvider.GetRequiredService<ISomeService>();
        service.DoYourMagic();
    }
    
  • 请注意,使用 ASP.NET Core 时,您通常不必手动创建新范围 - 每个 Web 请求都会自动获得自己的范围。您的所有类都是自动从范围请求的,并且该范围会在 Web 请求结束时自动清理。
于 2020-04-21T19:39:13.463 回答