我正在编写一个针对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
调用用于测试/调查目的。在MyHomeContext
I中,出于测试目的,实现了析构函数和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请求结束时会自动清理该作用域