我正在使用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; } } }
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。