【问题标题】:How to create a Ninject custom scope that returns the same object until that object is disposed?如何创建一个返回相同对象的 Ninject 自定义范围,直到该对象被释放?
【发布时间】:2011-04-26 01:11:58
【问题描述】:

在 Ninject 中,在单例范围内声明绑定意味着每次都将返回相同的对象。永远只能有一个对象。

我想要的是一次返回一个对象。换句话说:

  1. 第一次调用 Get() 实例化一个新对象并返回它。
  2. 对 Get() 的后续调用返回相同的实例。
  3. 对象已被释放。
  4. 在对象被释放后对 Get() 的第一次调用会实例化一个新的/第二个对象并返回它。
  5. 对 Get() 的后续调用返回在步骤 4 中创建的对象。

编辑: 这个问题实际上很容易解决,使用providers 并让有问题的对象在处置时引发事件。我很好奇是否有办法在 Ninject 中使用作用域来做到这一点,并将这个问题留在这里,因为 Steven 的回答非常好。

【问题讨论】:

  • 您正在运行什么类型的应用程序?它是多线程应用程序吗?
  • 是的。像每线程实例这样的东西在这里没有用。

标签: dependency-injection scope inversion-of-control ninject


【解决方案1】:

由于您想在多线程应用程序中使用此构造并希望跨线程重用相同的实例(正如您在评论中暗示的那样),您将无法通过配置 DI 容器来解决此问题。

由于竞争条件,您根本无法将对象配置为在处置后更新。想象以下场景:

  1. 线程 1 向容器请求实例。
  2. 这是第一个请求,容器将创建一个新实例。
  3. 线程 2 向容器请求实例
  4. 容器返回步骤 2 中创建的实例。
  5. 线程 1 处理完实例并调用 Dispose
  6. 线程 2 开始使用实例,但实例已被释放,并引发异常。

问题是应用程序将获得对可以释放的实例的引用。

如果可以,请尝试通过重新设计您的应用程序来防止这种情况发生。公开实现IDisposable 的服务类型是一种不好的做法,因为IDisposable 是一种泄漏抽象。我个人的偏好甚至是阻止这些服务的任何实现来实现IDisposable。在大多数情况下,重新设计可以让您不必这样做。

如果您需要使用IDisposable 对象,通常的方法是创建和注入创建这些IDisposable 对象的工厂。这样消费者就可以安全地处置这样的对象,没有任何问题。

这里的一般问题是很难创建实现IDisposable 的对象,实际上是线程安全的。

如果你真的想要这个,你可以尝试创建一个做引用计数的装饰器。例如看看下面的装饰器。它包装了IService 并实现了IServiceIService 实现 IDisposable。装饰器接受一个允许创建实例的Func<IService> 委托。对象的创建和处置受lock 语句的保护,并且装饰器计算调用者对它的引用。在最后一个消费者处理完装饰器之后,它将处理对象并创建一个新对象。

public class ScopedServiceDecorator : IService
{
    private readonly object locker = new object();
    private Func<IService> factory;
    private IService currentInstance;
    private int referenceCount;

    public ScopedServiceDecorator(Func<IService> factory)
    {
        this.factory = factory;
    }
    public void SomeOperation()
    {
        IService instance;
        lock (this.locker)
        {
            instance = this.GetInstance();
            this.referenceCount++;
        }

        instance.SomeOperation();
    }

    public void Dispose()
    {
        IService instance = null;

        lock (this.locker)
        {
            this.referenceCount--;

            if (this.referenceCount == 0)
            {
                instance = this.wrappedService;
                this.wrappedService = null;
            }
        }

        // Dispose the object outside the lock for performance.
        if (instance != null)
        {
            instance.Dispose();
        }
    }

    private IService GetInstance()
    {
        if (this.wrappedService == null)
        {
            this.wrappedService = this.factory();
        }

        return this.wrappedService;
    }
}

请注意,此实现仍有缺陷,原因如下:

  1. 多次调用Dispose 会破坏装饰器。
  2. 当消费者多次调用SomeOperation(或IService 有多个方法)时,实现将中断。

创建一个按预期工作的装饰器是相当困难的。一种简单的方法是序列化对对象的访问,但是当您这样做时,您可能希望每个线程使用一个实例。那会容易得多。

我希望这会有所帮助。

【讨论】:

【解决方案2】:

我知道这已经解决了,但是...@Steven 的回答并没有指出 Ninject 中有一个 InScope 机制可以解决您正在寻找的各个方面。

查看 Nate Kohari 的 Cache and Collect 文章,了解如何在 Ninject 2 中进行范围界定。

接下来,去查看 ninject 源码,看看 InRequestScope 是如何实现的(包括拆解是如何挂钩的)。为 2.3-4 计划了一些工作,以概括其工作原理,以允许将其用于一些复杂的托管场景。

当您查看了这两个参考资料后,请在 ninject 邮件列表中提问,您一定会找到解决方案的。

【讨论】:

  • 很遗憾,链接已损坏。
  • @samuel 替换为 archive.org 链接
猜你喜欢
  • 2011-09-08
  • 1970-01-01
  • 1970-01-01
  • 2016-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多