为什么GetService创建的对象没有被销毁



我正在编写一个针对dotnet核心框架3.1的应用程序。我使用依赖项注入来配置数据库上下文等。在我的程序.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保留了一个大的缓存。我原以为处理对象会释放内存,但现在我只会让情况变得更糟:(

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

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

  • 在调试器中运行的未优化(调试构建(.NET应用程序的行为与未附加调试器的优化应用程序截然不同。首先,在调试时,方法的所有变量都将始终被引用。这意味着对GC.Collect()的任何调用都将无法清除同一方法引用的context变量
  • 当Dispose模式正确实现时,当调用其Dispose方法时,类将抑制对终结器的调用。这是通过调用GC.SuppressFinalize.Entity Framework的DbContext正确实现Dispose模式来完成的,这也会导致您不会看到终结器被击中
  • 终结器(析构函数(是在称为终结器线程的后台线程上调用的。这意味着,即使您的context被取消引用并且符合垃圾收集的条件,也不太可能在调用GC.Collect()之后立即调用终结器。但是,您可以通过调用GC.WaitForPendingFinalizers((来停止应用程序并等待终结器被调用。在生产中,调用WaitForPendingFinalizers几乎不是您想要做的事情,但它对于测试和基准测试非常有用

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

  • 从DI容器解析的服务不应直接处理。相反,由于DI容器可以控制其创建,因此您也应该让它控制其销毁
  • 实现这一点的方法(使用MS.DI(是创建一个IServiceScope。服务被缓存在这样的范围内,当该范围被处理时,它将确保其缓存的一次性服务也被处理,并确保按相反的创建顺序进行
  • 直接从根容器(本例中为Host.Services(请求服务是个坏主意,因为这会导致作用域服务(如DbContext(缓存在根容器中。这使得他们有效地成为单身汉。换句话说,在应用程序期间,无论您从Host.Services请求相同的DbContext实例的频率如何,都将重用该实例。这可能会导致各种难以调试的问题。同样,解决方案是创建一个作用域并从该作用域进行解析。示例:
    var factory = Host.Services.GetRequiredService<IServiceScopeFactory>();
    using (var scope = factory.CreateScope())
    {
    var service = scope.ServiceProvider.GetRequiredService<ISomeService>();
    service.DoYourMagic();
    }
    
  • 请注意,使用ASP.NET Core时,通常不必手动创建新的作用域——每个web请求都会自动获得自己的作用域。所有的类都是从一个作用域自动请求的,并且在web请求结束时会自动清理该作用域

相关内容

  • 没有找到相关文章

最新更新