NamedScope 和垃圾收集

2024-02-05

(这个问题首先是在 Ninject Google Group 中提出的,但我现在发现 Stackoverflow 似乎更活跃。)

我使用 NamedScopeExtension 将相同的 ViewModel 注入到 View 和 Presenter 中。释放 View 后,内存分析显示 ViewModel 仍保留在 Ninject 缓存中。如何让 Ninject 释放 ViewModel?当表单关闭和处置时,所有 ViewModel 都会被释放,但我正在使用表单中的工厂创建和删除控件,并且希望 ViewModel 被垃圾收集到(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;
        }
    }
}

dotMemory.Check 断言失败,在分析快照时,ViewModel 引用了 Ninject 缓存。我认为命名范围应该在视图被释放时被释放。

问候, 安德烈亚斯


TL;DR

简短回答:添加INotifyWhenDisposed给你的View。处置视图。这将导致 ninject 自动处理所有绑定的东西InNamedScope另外 ninject 也会取消引用这些对象。这将导致(最终)垃圾收集(除非您在其他地方坚持强引用)。


为什么你的实施不起作用

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

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

现在这到底意味着什么?

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

确定性地释放作用域对象

现在,如果您需要在释放范围时立即处置/释放对象,则需要添加INotifyWhenDisposed https://github.com/ninject/Ninject/blob/master/src/Ninject/Infrastructure/Disposal/INotifyWhenDisposed.cs范围对象(另见here https://groups.google.com/forum/#!topic/ninject/usm3b93ZYw4)。 对于命名范围,您需要将此接口添加到绑定的类型中DefinesNamedScope- 在你的情况下View.

根据 Ninject.Extensions.NamedScope 的集成测试,这就足够了:请参阅here https://github.com/ninject/Ninject.Extensions.NamedScope/blob/c8d8e4b99a1add1fc11f455034343606f1538b50/src/Ninject.Extensions.NamedScope.Test/NamedScopeIntegrationTest.cs#L84

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

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

注意:如果根对象被绑定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 and 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;
        }
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

NamedScope 和垃圾收集 的相关文章

随机推荐

  • iOS swift 流媒体应用程序无法在后台模式下播放音乐

    我的应用程序运行良好 但一旦屏幕安全打开或在 iPhone 上执行其他操作 流就会停止 我激活了后台模式 正在播放音频 但这没有帮助 这是我的 ViewController swift import UIKit import MediaPl
  • HTML 5 是否需要 ``

    当编写 html 5 文档类型时 您是否应该包含就像您之前使用 HTML4 doctype 时所做的那样 还是应该使用不同的 xhtml HTML5 不需要使用xmlns属性 因为它是 XHTML 特有的 这意味着甚至 HTML 4 也不使
  • PHP - 魔术引号 gpc 和 stripslashes 问题

    好的 我的托管公司有magic quotes gpc turned ON我使用以下代码编写了我的 PHP 脚本stripslashes 在此准备过程中 但现在托管公司表示将转向magic quotes gpc关闭 我想知道现在我的数据会发生
  • 类型同义词对类型类的实例有什么影响? GHC 中的 TypeSynonymInstances 编译指示有何作用?

    我正在阅读现实世界哈斯克尔第151页 我盯着下面这段话看了一个多小时 回想一下 字符串是以下的同义词 Char 它又是类型 a 其中 Char 替换为类型 参数a 根据 Haskell 98 的 规则 我们不允许提供 在以下情况下用类型代替
  • 用 .NET 编写的服务可以自行终止吗?

    我有一个用 C 编写的服务应用程序 在某些情况下 我希望它自行终止 这会在服务运行一段时间后发生 因此在 OnStart 事件中不会发生这种情况 到目前为止我读到的所有内容都表明终止服务的唯一安全方法是通过服务控制管理器 我的服务作为本地服
  • 单元测试实体框架

    我刚刚开始使用 Entity Framework v4 和 Linq 我有一个实体数据模型 它是从数据库生成的 然后 我实现了存储库类 以便实现我的实体的业务逻辑 它们包含用于与实体 数据库交互的 LINQ 查询 在不访问数据库的情况下对我
  • delphi 对象赋值与:=

    有人可以解释一下以下之间的区别 1 newObj TMyObject Create newObj Assign oldObj and 2 newObj oldObj 2 确实newObj and oldObj引用同一个对象 抱歉 如果之前已
  • acts_as_taggable_on 标签添加两次

    我有一个 RoR 应用程序 允许用户标记其集合中的项目 我使用 tag it js Jquery 插件并使用 Ajax 调用在 ItemsController 中添加和删除标签 我的问题是每个标签添加两次 因此当我执行 item tags
  • CSS中不同高度的块的垂直对齐[重复]

    这个问题在这里已经有答案了 我试图达到类似的效果this https blaskdemo wordpress com 也就是说 我有一些块 这里 articles 具有相同的宽度但可以具有不同的高度 并且我希望它们位于其上邻居的旁边 当内联
  • 如何在 WCF 数据服务中获得内部联接

    假设我有 2 个表 table1 和 table2 具有共享密钥 id 如果我想使用 sql 对这两个表进行内部联接 我会做类似的事情 select id x y z from table1 inner join table2 on tab
  • play 2.4 中的插件、依赖项、模块和子项目有什么区别?

    我是 playframework 的新手 刚刚学习 我对依赖项 模块 插件和子项目有点困惑 它们有何不同 这是我的理解 可能是错的 依赖项 是播放应用程序运行所需的所有库 子项目 是另一个父应用程序内的播放应用程序 不确定 插件 和 模块
  • Rescue_from 不会从视图或助手中拯救 Timeout::Error

    我的应用程序控制器中有一个 around filter 用于将所有操作封装在超时块中 以便操作在达到 30 秒 Heroku 限制之前失败 我还有一个rescue from Timeout Error 来彻底挽救这些超时 不幸的是 resc
  • 允许在 React Native 中关注 TextInput 时点击/按下项目

    我有一个TextInput其功能是对某些结果进行搜索 过滤 结果显示在ScrollView 我遇到的问题是 虽然国家focus on the TextInput 用户必须点击两次才能选择该项目 这是一个TouchableOpacity 在里
  • Laravel 中的一次性自定义 cron 计划

    我想在用户在表单中输入的自定义日期和时间运行一次 cron 做这个的最好方式是什么 我发现可以像这样在 laravel 中安排自定义 cron gt cron 按照自定义 Cron 计划运行任务 但我找不到时间格式 的含义 或者更简单 可以
  • Numpy-convertible 类可以从序列内部正确转换为 ndarray?

    The array 方法允许自定义类型自动转换为 numpy 例如 gt gt gt class Convertible def array self return np zeros 7 gt gt gt np array Converti
  • 改变spacy NER中的beam_width

    我想将 nlp entity cfg beam width 默认情况下为 1 更改为 3 我尝试了 nlp entity cfg update beam width 3 但看起来 nlp 的东西在这次更改后被破坏了 如果我执行nlp str
  • 如何为 IP 地址签署 SSL 证书? [关闭]

    Closed 这个问题是与编程或软件开发无关 help closed questions 目前不接受答案 我有一台服务器 在我家里的一台机器上仅托管我网站的节点后端 我正在使用express 我想从另一个后端调用该服务器 我们正在尝试构建一
  • Java(Android Studio)libgdx中的代码,如何计算弹丸[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 Java Android Studio libgdx中的代码 当您单击 触摸屏幕时 如何计算圆形 如球 的射弹以及如何显示它 就像打篮
  • 电子邮件模板位置绝对吗?

    使用安全吗position absolute在电子邮件模板中 取决于您的用户使用的邮件客户端 例如 Outlook 处理位置 绝对好 而 Thunderbird 则不然 我会尝试将您的邮件模板设计得尽可能 正常 例如 表格有很大帮助 恶心
  • NamedScope 和垃圾收集

    这个问题首先是在 Ninject Google Group 中提出的 但我现在发现 Stackoverflow 似乎更活跃 我使用 NamedScopeExtension 将相同的 ViewModel 注入到 View 和 Presente