为什么 GetService 创建的对象没有被销毁?
Why is an object created by GetService not being destructed?
我正在编写一个针对 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
传给另一个class。在另一个 class 中,作为较长方法的一部分,我有以下代码:
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
的析构函数。在我看来,依赖注入框架中的某些东西持有对该对象的引用。这是真的?如果是这样,我如何告诉它释放它?
很难很好地回答您的问题,因为有很多误解需要解决。以下是一些需要注意的事项:
- 运行 在调试器中的未优化(调试构建).NET 应用程序的行为与未附加调试器的优化应用程序有很大不同。首先,在调试时,方法的所有变量将始终保持引用状态。这意味着对
GC.Collect()
的任何调用都无法清除由同一方法引用的 context
变量。
- 当Dispose Pattern is implemented correctly, a call to the finalizer will be suppressed by the class when its
Dispose
method is called. This is done by calling GC.SuppressFinalize。 Entity Framework 的 DbContext
正确实现了 Dispose 模式,这也会导致您看不到终结器被命中。
- 在名为 finalizer thread. This means that even if your
context
was de-referenced and was eligible for garbage collection, the finalizer is unlikely to be called immediately after the calls to GC.Collect()
. You can, however, halt your application and wait until the finalizers are called by calling GC.WaitForPendingFinalizers() 的后台线程上调用终结器(析构函数)。调用 WaitForPendingFinalizers
几乎不是你想在生产中做的事情,但它可以用于测试和基准测试目的。
除了这些 CLR 特定部分之外,这里还有一些关于 DI 部分的反馈:
- 不应直接处置从 DI 容器解析的服务。相反,由于 DI 容器控制着它的创建,你也应该让它控制它的销毁。
- 执行此操作的方法(使用 MS.DI)是创建一个
IServiceScope
。服务缓存在这样的范围内,当范围被释放时,它会确保其缓存的一次性服务也被释放,并且它会确保以相反的创建顺序完成。
- 直接从根容器(
Host.Services
在您的情况下)请求服务不是一个好主意,因为它会导致作用域服务(例如您的 DbContext
)缓存在根容器中。这使他们有效地成为单身人士。换句话说,同一个 DbContext
实例将在应用程序期间重复使用,无论您从 Host.Services
请求它的频率如何。这可能导致 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 请求都会自动获得自己的范围。您的所有 类 都是从范围自动请求的,并且该范围会在网络请求结束时自动清理。
我正在编写一个针对 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
传给另一个class。在另一个 class 中,作为较长方法的一部分,我有以下代码:
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
的析构函数。在我看来,依赖注入框架中的某些东西持有对该对象的引用。这是真的?如果是这样,我如何告诉它释放它?
很难很好地回答您的问题,因为有很多误解需要解决。以下是一些需要注意的事项:
- 运行 在调试器中的未优化(调试构建).NET 应用程序的行为与未附加调试器的优化应用程序有很大不同。首先,在调试时,方法的所有变量将始终保持引用状态。这意味着对
GC.Collect()
的任何调用都无法清除由同一方法引用的context
变量。 - 当Dispose Pattern is implemented correctly, a call to the finalizer will be suppressed by the class when its
Dispose
method is called. This is done by calling GC.SuppressFinalize。 Entity Framework 的DbContext
正确实现了 Dispose 模式,这也会导致您看不到终结器被命中。 - 在名为 finalizer thread. This means that even if your
context
was de-referenced and was eligible for garbage collection, the finalizer is unlikely to be called immediately after the calls toGC.Collect()
. You can, however, halt your application and wait until the finalizers are called by calling GC.WaitForPendingFinalizers() 的后台线程上调用终结器(析构函数)。调用WaitForPendingFinalizers
几乎不是你想在生产中做的事情,但它可以用于测试和基准测试目的。
除了这些 CLR 特定部分之外,这里还有一些关于 DI 部分的反馈:
- 不应直接处置从 DI 容器解析的服务。相反,由于 DI 容器控制着它的创建,你也应该让它控制它的销毁。
- 执行此操作的方法(使用 MS.DI)是创建一个
IServiceScope
。服务缓存在这样的范围内,当范围被释放时,它会确保其缓存的一次性服务也被释放,并且它会确保以相反的创建顺序完成。 - 直接从根容器(
Host.Services
在您的情况下)请求服务不是一个好主意,因为它会导致作用域服务(例如您的DbContext
)缓存在根容器中。这使他们有效地成为单身人士。换句话说,同一个DbContext
实例将在应用程序期间重复使用,无论您从Host.Services
请求它的频率如何。这可能导致 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 请求都会自动获得自己的范围。您的所有 类 都是从范围自动请求的,并且该范围会在网络请求结束时自动清理。