【问题标题】:Ninject Factory Extension and Dealing with Memory LeakNinject 工厂扩展和内存泄漏处理
【发布时间】:2015-11-12 16:15:25
【问题描述】:

这个问题更像是一个“我该怎么做?”,而不是一个“我做错了什么?”。我有一个名为 QueryProcessor 的类来处理查询(想想 CQRS)。该对象被注入到我的演示者中。 QueryProcessor 需要使用内核来解析绑定。直接或通过工厂注入内核很容易。这样做不会导致内存泄漏是诀窍。

我已经使用内存分析器验证了我的 QueryProcessor 对象都没有被垃圾回收。该类如下所示:

public sealed class QueryProcessor : IQueryProcessor, IDisposable
{
    private readonly IKernelFactory _container;
    private bool _disposed;

    public QueryProcessor(IKernelFactory container)
    {
        _container = container;
    }

    //[DebuggerStepThrough]
    public TResult Process<TResult>(IQuery<TResult> query)
    {
        var handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));

        dynamic handler = _container.RetrieveKernel().Get(handlerType);

        return handler.Handle((dynamic)query);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (disposing && !_disposed)
        {
            // dispose of stuff here
            _disposed = true;
        }            
    }
}

public interface IKernelFactory
{
    IKernel RetrieveKernel();
}

我的作文根相当简单。我正在使用 Ninject 的工厂扩展。

public void OnLoad(IKernel kernel)
{
    //  Auto-Register all the validators which are stored in the Service assembly.
    AssemblyScanner.FindValidatorsInAssembly(_serviceAssembly).ForEach(
            result => kernel.Bind(result.InterfaceType, result.ValidatorType)
        );

    ManualRegistrations(kernel);

    kernel.Bind<IKernelFactory>().ToFactory();

    AutoRegisterType(kernel, typeof(IQueryHandler<,>));
    AutoRegisterType(kernel, typeof(ICommandHandler<>));
}

如前所述,注入是有效的,但它会留下内存泄漏。我应该如何让 Ninject 内核解析我的 QueryProcessor 中的内容而不导致泄漏?

谢谢

更新 - 新问题

我试图通过创建一个带有新模块的新内核来解决这个问题,该模块与合成根的主内核分开。这些子内核将被创建和处理,它们的生命周期与 QueryProcessor 的生命周期相关联。我在主模块中是这样连接的:

kernel.Bind<IQueryProcessor>().ToMethod(ctx => new QueryProcessor(new StandardKernel(new ProcessorModule(_serviceAssembly)))).InTransientScope();

在第一次处理内核之前它可以正常工作。但在那之后,我收到以下错误消息:

Error loading Ninject component ICache
No such component has been registered in the kernel's component container.

Suggestions:
  1) If you have created a custom subclass for KernelBase, ensure that you have properly
     implemented the AddComponents() method.
  2) Ensure that you have not removed the component from the container via a call to RemoveAll().
  3) Ensure you have not accidentally created more than one kernel.    

如果我这样做该死,如果我不这样做该死......

【问题讨论】:

  • 如何绑定QueryProcessor?谁在处理查询处理器?
  • 还要注意,如果没有内存压力,垃圾收集器不收集对象是完全合法的。等到您不再引用某个对象,然后将其视为内存泄漏是无效。那么你有什么证据证明确实存在内存泄漏?由于您使用了内存分析器,请向我们展示QueryProcessor 的 GC 根的所有路径(应该收集但不收集)。
  • @BatteryBackupUnit QueryProcessor 与 kernel.bind.To() 绑定,并由 Presenter 在各自的 Dispose 方法中处理。但是,我不能处理内核,因为其他地方需要它。注意,这不是一个 web 项目,所以内核需要比 http 请求/响应更长的寿命。今晚我将尝试从分析器中获取一些数据。但它确实有一个强制 GC 的按钮,并且该按钮对所有其他 gen 1 对象有效,这些对象在 GC 之后从下一个内存快照中消失。
  • 使用此配置,ninject 不会保留对QueryProcessor 的引用。如果存在内存泄漏,则它必须影响更多类型(例如,绑定 InSingletonScope() 的东西会保留在 QueryProcessors 上)或者内存泄漏是由于您的代码的某些部分造成的。
  • 旁注:即使对于 Web 应用程序,为每个请求重新创建内核也不是一个好主意(性能方面)。虽然有些人可能仍然这样做,但肯定有很多人不这样做。

标签: c# dependency-injection ninject ninject-extensions


【解决方案1】:

由于您的应用程序(而不是 DI 容器)正在创建实例,因此它还负责处置该实例。这种情况可以通过使用 Register、Resolve 和 Release 模式来处理。

如果你注入内核,那么你已经有效地实现了service locator anti-pattern。这意味着您的应用程序显式依赖于您的 DI 框架。

您应该使用DI Friendly Framework 中提到的抽象工厂来处理创建和释放处理程序实例,而不是注入内核。

public interface IHandlerFactory
{
    dynamic Create(Type handlerType);

    void Release(dynamic handler);
}

public interface HandlerFactory
{
    private readonly Func<Type, dynamic> handlerMethod;

    public HandlerFactory(Func<Type, dynamic> handlerMethod)
    {
        if (handlerMethod == null)
            throw new ArgumentNullException("handlerMethod");

        this.handlerMethod = handlerMethod;
    }

    public dynamic Create(Type handlerType)
    {
         return handlerMethod(handlerType);
    }

    public void Release(dynamic handler)
    {
        IDisposable disposable = handler as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }
}

用法

public sealed class QueryProcessor : IQueryProcessor
{
    private readonly IHandlerFactory handlerFactory;

    public QueryProcessor(IHandlerFactory handlerFactory)
    {
        if (handlerFactory == null)
            throw new ArgumentNullException("handlerFactory");

        this.handlerFactory = handlerFactory;
    }

    //[DebuggerStepThrough]
    public TResult Process<TResult>(IQuery<TResult> query)
    {
        var handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));

        dynamic handler = this.handlerFactory.Create(handlerType);
        try
        {
            return handler.Handle((dynamic)query);
        }
        finally
        {
            this.handlerFactory.Release(handler);
        }
    }
}

请注意,如果您使用这种方法,您不需要每个处理程序都实现IDisposable,您的QueryProcessor 也不需要实现IDisposable

在您的组合根中,您只需要实现处理程序方法并将其作为参数注册到您的工厂。

Func<Type, dynamic> handlerMethod = type => (dynamic)kernel.Resolve(type);
kernel.Bind<IHandlerFactory>().To<HandlerFactory>()
    .WithConstructorArgument("handlerMethod", handlerMethod);

当然,如果您正在异步处理您的处理程序,您必须让实例保持活动状态直到请求结束,而不是在 handler.Handle() 方法返回后立即释放它。如果是这种情况,我建议您分析source code for WebApi 以找出在处理System.Web.Http.ApiController 时使用什么模式。

【讨论】:

  • 我应该提到,这是一个 Winforms 应用程序,而不是 Web。所以,生命周期有点棘手。我正在使用工厂扩展,您可以在我的代码中看到。我不知道如何在不杀死内核的情况下杀死 QueryProcessor。我知道我可以给 QueryProcessor 一个 Singleton 生命周期。但这可能不是 ThreadSafe 选项。
猜你喜欢
  • 2011-09-16
  • 1970-01-01
  • 1970-01-01
  • 2018-05-30
  • 2019-07-16
  • 1970-01-01
  • 2018-05-19
  • 1970-01-01
  • 2015-06-14
相关资源
最近更新 更多