c# - NamedScope 和垃圾回收

标签 c# garbage-collection ninject ninject-extensions

(这个问题最初是在 Ninject Google Group 中提出的,但我现在看到 Stackoverflow 似乎更活跃。)

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

有关问题的说明,请参阅以下使用 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 添加到您的 View。处理 View 。这将导致 ninject 自动处理所有绑定(bind) InNamedScope 的东西,而且 ninject 将取消引用这些对象。这将导致(最终)垃圾收集(除非您在其他地方坚持使用强引用)。


为什么你的实现不起作用

当 View 被释放/被处置时,Ninject 不会得到通知。 这就是为什么 ninject 有一个计时器运行来检查范围对象是否仍然存在(事件 = 未被垃圾收集)。如果作用域对象不再存在,它会处理/释放作用域中保存的所有对象。

我相信计时器默认设置为 30 秒。

现在这到底是什么意思?

  • 如果没有内存压力,GC 可能会花费很长时间直到范围对象被垃圾回收(或者他可能永远不会这样做)
  • 一旦范围对象被垃圾回收,可能需要大约 30 秒的时间来处理和释放范围对象
  • 一旦 ninject 释放范围对象,如果没有内存压力,GC 可能需要很长时间来收集对象。

确定性地释放作用域对象

现在,如果您需要在释放作用域时立即处置/释放对象,则需要添加 INotifyWhenDisposed到范围对象(另见 here )。 对于命名范围,您需要将此接口(interface)添加到与 DefinesNamedScope 绑定(bind)的类型 - 在您的例子中是 View

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

注意:唯一真正确定的是作用域对象的处置。 实际上,这通常也会显着缩短垃圾收集的时间。但是,如果没有内存压力,那么实际的收集仍然需要很长时间。

实现这个应该让单元测试通过。

注意:如果根对象绑定(bind)了 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();
    }
}

它甚至可以与 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<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/

相关文章:

c# - 如何获取 Windows 服务登录用户的安全 token ?

javascript - GC 期间会删除什么类型的 cookie?

assemblies - NInject 可以按需加载模块/程序集吗?

c# - 是否保证多个 ninject 绑定(bind)保持其绑定(bind)顺序

java - 添加soap header 的C#代码 wsse :Security, wsse :BinarySecurityToken, ds :Signature, wsse :UsernameToken, wsu:Timestamp

c# - 来自应用程序的错误 : Could not extract Info. plist:无法将 plist 文件 '/Pathof.appfile/Info.plist' 解析为 XML

c# - MySQL 的 Linq To Entities DayOfYear 替代方案

java - 在 Android 应用程序中释放共享资源

java - 时隙系统中的垃圾收集

asp.net-mvc - Ninject.Web.Common 抛出 ActivationException 试图将依赖项注入(inject) HttpApplicationInitializationHttpModule