NamedScope and garbage collection

(This question was first asked by the Ninject Google Group, but now I see that Stackoverflow seems to be more active.)

I use NamedScopeExtension to input the same ViewModel in both the view and the presenter. After the view has been released, memory profiling indicates that the ViewModel is still stored by the Ninject cache. How can I make Ninject a release of ViewModel? All ViewModels are freed when the form is closed and deleted, but I create and delete controls using Factory on the form and would like the ViewModels objects to be garbage collected (the presenter and view are collected).

To illustrate the problem, see the following UnitTest using 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; } } } 

The error is dotMemory.Check assert, and when analyzing the snapshot, ViewModel has links to the Ninject cache. I thought that a named scope should be released when releasing View.

Regards, Andreas

+6
source share
1 answer

TL DR

short answer: add INotifyWhenDisposed to your View . Dispose of the view. This will cause ninject to automatically delete all related InNamedScope plus things, and also ninject will not reference these objects. This will result in (final) garbage collection (unless you approach strong links elsewhere).


Why your implementation does not work

Ninject does not receive information when a view is released / deleted. Therefore, ninject has a timer that checks if the scope object is alive (live = not garbage collected). If the scope object is no longer alive, it allocates / frees all objects that have been stored in scope.

I assume the default timer is set to 30 seconds.

Now what does that mean?

  • If there is no memory pressure, the GC may take a long time until the area object is garbage collected (or it may not, someday).
  • After the object-object is collected using garbage, it may take about 30 seconds to place objects with areas and release them.
  • After ninject has released scope objects, again the GC can take a long time to collect the object if there is no memory pressure.

Deterministic selection of objects with an area

Now, if you need the objects to be deleted or released immediately after releasing the area, you need to add INotifyWhenDisposed to (also see here ). Using these areas, you will need to add this interface to the type that is associated with DefinesNamedScope - in your case, View .

According to the integration tests Ninject.Extensions.NamedScope this will be enough: see here

Note. The only thing that is really determined is the removal of objects with scope. In practice, this usually reduces garbage collection time. However, if there is no memory pressure, again, the actual collection can take a long time.

Implementing this should pass the unit test.

Note: if the root object is bound by InCallScope , then this solution does not work (ninject 3.2.2 / NamedScope 3.2.0). I think this is due to an error with InCallScope , but, unfortunately, I was not able to report this (error) several years ago. I could be wrong too.


Evidence that the INotifyWhenDisposed implementation on the root will have children

 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(); } } 

It even works with DefineDependency and WithCreatorAsConstructorArgument

I don't have dotMemory.Unit, but this checks if ninject keeps a strong reference to objects in the cache:

 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; } } } 
+6
source

All Articles