NamedScope和垃圾收集

(这个问题首先在Ninject Google Group中提出,但我现在看到Stackoverflow似乎更活跃了。)

我正在使用NamedScopeExtension将相同的ViewModel注入View和Presenter。 释放View后,内存分析显示Ninject缓存仍保留ViewModel。 如何让Ninject释放ViewModel? 当Form关闭和处置时,所有ViewModel都会被释放,但我正在使用Form中的Factory创建和删除Controls,并希望将ViewModel垃圾收集到(收集Presenter和View)。

有关问题的说明,请参阅以下UnitTest,使用dotMemoryUnit:

using System; using FluentAssertions; using JetBrains.dotMemoryUnit; using Microsoft.VisualStudio.TestTools.UnitTesting; using Ninject; using Ninject.Extensions.DependencyCreation; using Ninject.Extensions.NamedScope; namespace UnitTestProject { [TestClass] [DotMemoryUnit(FailIfRunWithoutSupport = false)] public class UnitTest1 { [TestMethod] public void TestMethod() { // Call in sub method so no local variables are left for the memory profiling SubMethod(); // Assert dotMemory.Check(m => { m.GetObjects(w => w.Type.Is()).ObjectsCount.Should().Be(0); }); } private static void SubMethod() { // Arrange var kernel = new StandardKernel(); string namedScope = "namedScope"; kernel.Bind().ToSelf() .DefinesNamedScope(namedScope); kernel.DefineDependency(); kernel.Bind().ToSelf() .InNamedScope(namedScope); kernel.Bind().ToSelf() .WithCreatorAsConstructorArgument("view"); // Act var view = kernel.Get(); kernel.Release(view); } } public class View { public View() { } public View(ViewModel vm) { ViewModel = vm; } public ViewModel ViewModel { get; set; } } public class ViewModel { } public class Presenter { public View View { get; set; } public ViewModel ViewModel { get; set; } public Presenter(View view, ViewModel viewModel) { View = view; ViewModel = viewModel; } } } 

dotMemory.Check断言失败,在分析快照时,ViewModel引用了Ninject缓存。 我认为在View发布时应该发布命名范围。

此致,安德烈亚斯

TL; DR

简短回答:将INotifyWhenDisposed添加到您的View 。 处理视图。 这将导致ninject自动处理绑定InNamedScope所有东西以及ninject将取消引用这些对象。 这将导致(最终)垃圾收集(除非您在其他地方依赖强引用)。


为什么你的实现不起作用

当视图被释放/被处置时,Ninject不会得到通知。 这就是为什么ninject运行一个定时器来检查scope-object是否仍然存活(alive = not garbage collecting)。 如果scope-object不再存在,它会处置/释放范围内保存的所有对象。

我相信默认情况下,计时器设置为30秒。

那究竟是什么意思呢?

  • 如果没有内存压力,GC可能需要很长时间,直到范围对象被垃圾收集(或者他可能不会这样做)
  • 一旦scope-object被垃圾收集,scoped对象也可能需要大约30秒才能被处理和释放
  • 一旦ninject释放了范围对象,再次,如果没有内存压力,GC可能需要很长时间才能收集对象。

确定性地释放Scoped对象

现在,如果您需要在释放范围时立即释放/释放对象,则需要将INotifyWhenDisposed添加到范围对象(另请参见此处 )。 对于命名范围,您需要将此接口添加到与DefinesNamedScope绑定的类型 – 在您的情况下为View

根据Ninject.Extensions.NamedScope的集成测试,这就足够了:见这里

注意:唯一真正确定的是处理范围对象。 在实践中,这通常也会显着缩短垃圾收集的时间。 但是,如果没有内存压力,实际的收集仍然需要很长时间。

实现这一点应该让unit testing通过。

注意:如果根对象绑定了InCallScope则此解决方案不起作用(ninject 3.2.2 / NamedScope 3.2.0)。 我认为这是由于InCallScope一个错误,但遗憾的是我几年前没有报告它(这个错误)。 不过,我可能也错了。


certificate在根对象中实现INotifyWhenDisposed将处置子级

 public class View : INotifyWhenDisposed { public View(ViewModel viewModel) { ViewModel = viewModel; } public event EventHandler Disposed; public ViewModel ViewModel { get; private set; } public bool IsDisposed { get; private set; } public void Dispose() { if (!this.IsDisposed) { this.IsDisposed = true; var handler = this.Disposed; if (handler != null) { handler(this, EventArgs.Empty); } } } } public class ViewModel : IDisposable { public bool IsDisposed { get; private set; } public void Dispose() { this.IsDisposed = true; } } public class IntegrationTest { private const string ScopeName = "ViewScope"; [Fact] public void Foo() { var kernel = new StandardKernel(); kernel.Bind().ToSelf() .DefinesNamedScope(ScopeName); kernel.Bind().ToSelf() .InNamedScope(ScopeName); var view = kernel.Get(); view.ViewModel.IsDisposed.Should().BeFalse(); view.Dispose(); view.ViewModel.IsDisposed.Should().BeTrue(); } } 

它甚至适用于DefineDependencyWithCreatorAsConstructorArgument

我没有dotMemory.Unit但是这会检查ninject是否保留对其缓存中对象的强引用:

 namespace UnitTestProject { using FluentAssertions; using Ninject; using Ninject.Extensions.DependencyCreation; using Ninject.Extensions.NamedScope; using Ninject.Infrastructure.Disposal; using System; using Xunit; public class UnitTest1 { [Fact] public void TestMethod() { // Arrange var kernel = new StandardKernel(); const string namedScope = "namedScope"; kernel.Bind().ToSelf() .DefinesNamedScope(namedScope); kernel.DefineDependency(); kernel.Bind().ToSelf().InNamedScope(namedScope); Presenter presenterInstance = null; kernel.Bind().ToSelf() .WithCreatorAsConstructorArgument("view") .OnActivation(x => presenterInstance = x); var view = kernel.Get(); // named scope should result in presenter and view getting the same view model instance presenterInstance.Should().NotBeNull(); view.ViewModel.Should().BeSameAs(presenterInstance.ViewModel); // disposal of named scope root should clear all strong references which ninject maintains in this scope view.Dispose(); kernel.Release(view.ViewModel).Should().BeFalse(); kernel.Release(view).Should().BeFalse(); kernel.Release(presenterInstance).Should().BeFalse(); kernel.Release(presenterInstance.View).Should().BeFalse(); } } public class View : INotifyWhenDisposed { public View() { } public View(ViewModel viewModel) { ViewModel = viewModel; } public event EventHandler Disposed; public ViewModel ViewModel { get; private set; } public bool IsDisposed { get; private set; } public void Dispose() { if (!this.IsDisposed) { this.IsDisposed = true; var handler = this.Disposed; if (handler != null) { handler(this, EventArgs.Empty); } } } } public class ViewModel { } public class Presenter { public View View { get; set; } public ViewModel ViewModel { get; set; } public Presenter(View view, ViewModel viewModel) { View = view; ViewModel = viewModel; } } }