我正在使用NamedScopeExtension将相同的viewmodel注入到View和Presenter中.发布View后,内存分析显示Ninject缓存仍保留viewmodel.如何让Ninject发布viewmodel?表单关闭和处置时释放所有viewmodel,但我使用表单中的Factory创建和删除控件,并希望将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<viewmodel>()).ObjectsCount.Should().Be(0); }); } private static void SubMethod() { // Arrange var kernel = new StandardKernel(); string namedScope = "namedScope"; kernel.Bind<View>().ToSelf() .DefinesNamedScope(namedScope); kernel.DefineDependency<View,Presenter>(); kernel.Bind<viewmodel>().ToSelf() .InNamedScope(namedScope); kernel.Bind<Presenter>().ToSelf() .WithCreatorAsConstructorArgument("view"); // Act var view = kernel.Get<View>(); 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发布时应该发布命名范围.
问候,
安德烈亚斯
解决方法
简短回答:将INotifyWhenDisposed添加到您的视图中.处理视图.这将导致ninject自动处理绑定InNamedScope的所有东西以及ninject将取消引用这些对象.这将导致(最终)垃圾收集(除非您在其他地方依赖于强引用).
为什么你的实现不起作用
当视图被释放/获得处置时,Ninject不会得到通知.
这就是为什么ninject运行一个定时器来检查scope-object是否仍然存活(alive = not garbage collecting).如果scope-object不再存在,它会处置/释放范围内保存的所有对象.
我相信默认情况下定时器设置为30秒.
那究竟是什么意思呢?
>如果没有内存压力,GC可能需要很长时间,直到范围对象被垃圾收集(或者他可能不会这样做)
>一旦scope-object被垃圾收集,scoped对象也可能需要大约30秒才能被处理和释放
>一旦ninject释放了范围对象,再次,如果没有内存压力,GC可能需要很长时间才能收集对象.
确定性地释放Scoped对象
现在,如果您需要在释放范围时立即处置/释放对象,则需要将INotifyWhenDisposed
添加到范围对象(另请参见here).
对于命名范围,您需要将此接口添加到与DefinesNamedScope绑定的类型 – 在您的情况下为View.
根据Ninject.Extensions.NamedScope的集成测试,这就足够了:见here
注意:唯一真正确定的是处理范围对象.
在实践中,这通常也会显着缩短垃圾收集的时间.但是,实际的收集仍然需要很长时间.
实现这个应该让单元测试通过.
注意:如果根对象绑定了InCallScope,则此解决方案不起作用(ninject 3.2.2 / NamedScope 3.2.0).我认为这是由于InCallScope的一个错误,但遗憾的是几年前我没有报告它(这个错误).不过,我可能也错了.
证明在根对象中实现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<View>().ToSelf() .DefinesNamedScope(ScopeName); kernel.Bind<viewmodel>().ToSelf() .InNamedScope(ScopeName); var view = kernel.Get<View>(); view.viewmodel.IsDisposed.Should().BeFalse(); view.Dispose(); view.viewmodel.IsDisposed.Should().BeTrue(); } }
它甚至适用于DefineDependency和WithCreatorAsConstructorArgument
我没有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<View>().ToSelf() .DefinesNamedScope(namedScope); kernel.DefineDependency<View,Presenter>(); kernel.Bind<viewmodel>().ToSelf().InNamedScope(namedScope); Presenter presenterInstance = null; kernel.Bind<Presenter>().ToSelf() .WithCreatorAsConstructorArgument("view") .OnActivation(x => presenterInstance = x); var view = kernel.Get<View>(); // 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; } } }