(这个问题最初是在ninject google组中提出的,但现在我看到stackoverflow似乎更加活跃了。)
我使用namedscopeextension将同一个viewmodel注入到视图和演示者中。在视图被释放之后,内存分析显示视图模型仍然由ninject缓存保留。如何使ninject发布viewmodel?当窗体关闭并释放时,所有的viewmodels都会被释放,但是我正在使用窗体中的工厂创建和删除控件,并且希望viewmodels被垃圾收集到(presenter和view被收集)。
有关问题的说明,请参阅以下使用dotmemoryunit的unittest:
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;
}
}
}
check assert失败,分析快照时viewmodel引用了ninject缓存。我认为当视图被释放时,命名的作用域应该被释放。
当做,
安德烈亚斯
最佳答案
TL;博士
简而言之:将INotifyWhenDisposed
添加到您的View
中。处理视图。这将导致ninject自动处理绑定InNamedScope
的所有内容,并且ninject将取消引用这些对象。这将导致(最终)垃圾回收(除非您在其他地方挂起了强引用)。
为什么你的实现不起作用
当视图被释放/释放时,ninject不会得到通知。
这就是为什么ninject会运行一个计时器来检查scope对象是否仍然存在(alive=not garbage collected)。如果作用域对象不再是活动的,它将处理/释放作用域中保存的所有对象。
我相信计时器默认设置为30秒。
这到底是什么意思?
如果没有内存压力,gc可能需要很长时间,直到scope对象被垃圾收集(或者他可能永远不会这样做)
一旦作用域对象被垃圾收集,处理和释放作用域对象可能需要大约30秒
一旦ninject释放了scope对象,同样,如果没有内存压力,gc可能需要很长时间来收集对象。
确定释放作用域对象
现在,如果需要在释放作用域时立即释放/释放对象,则需要将INotifyWhenDisposed
添加到作用域对象(另请参见here)。
对于命名作用域,您需要将此接口添加到与DefinesNamedScope
绑定的类型中—在您的情况下是View
。
根据ninject.extensions.namedscope的集成测试,这就足够了:请参见here
注意:唯一真正确定的是处理作用域对象。
实际上,这通常也会大大缩短垃圾收集的时间。然而,如果没有内存压力,实际的收集仍然可能需要很长时间。
实现这一点可以使单元测试通过。
注意:如果根对象被绑定InCallScope
,则此解决方案不起作用(ninject 3.2.2/namedscope 3.2.0)。我认为这是由于一个InCallScope
的bug造成的,但遗憾的是,几年前我没有报告这个bug。不过,我也可能弄错了。
证明在根对象中实现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;
}
}
}
关于c# - NamedScope和垃圾回收,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/32114221/