c# – NamedScope和垃圾回收

前端之家收集整理的这篇文章主要介绍了c# – NamedScope和垃圾回收前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
(这个问题最初是在Ninject Google Group中提出的,但我现在看到Stackoverflow似乎更活跃了.)

我正在使用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发布时应该发布命名范围.

问候,
安德烈亚斯

解决方法

TL; DR

简短回答:将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;
        }
    }
}

猜你在找的C#相关文章