如何创建一个 Ninject 自定义作用域,在该对象被释放之前返回相同的对象?

发布于 2024-11-03 01:48:46 字数 524 浏览 1 评论 0 原文

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

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

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

编辑: 使用 providers 并让相关对象在处置时引发事件。我很好奇是否有办法使用 Ninject 中的范围来做到这一点,并将这个问题留在这里,因为 Steven 的回答非常好。

In Ninject, declaring a binding in singleton scope means that the same object will be returned every time. There can only be one object, ever.

What I would like is to return one object at a time. In other words:

  1. The first call to Get() instantiates a new object and returns it.
  2. Subsequent calls to Get() return the same instance.
  3. The object is disposed.
  4. The first call to Get() after the object was disposed instantiates a new/second object and returns that.
  5. Subsequent calls to Get() return the object created in step 4.

EDIT: This problem is actually rather simple to solve using using providers and having the object in question raise an event when disposed. I was curious if there was a way to do this using scopes in Ninject, and will leave this question here because Steven's answer is excellent.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

中二柚 2024-11-10 01:48:46

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

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

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

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

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

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

这里的普遍问题是很难创建实现 IDisposable 的对象,而这些对象实际上是线程安全的。

如果你真的想要这个,你可以尝试创建一个执行引用计数的装饰器。例如,看看下面的装饰器。它包装了 IService 并实现了 IServiceIService 实现IDisposable。装饰器采用 Func> 。允许创建实例的委托。对象的创建和处置受到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 有多个方法)时,实现将会中断。

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

我希望这有帮助。

Since you want to use this construct in a multi-threaded application and want to reuse the same instance across threads (as you imply in your comment), you will not be able to solve this problem by configuring your DI container.

You simply can't configure the object to be renewed after disposal, because of race conditions. Imagine the following scenario:

  1. Thread 1 requests an instance from the container.
  2. This is the first request and the container will create a fresh instance.
  3. Thread 2 requests an instance from the container
  4. The container returns the instance created in step 2.
  5. Thread 1 is done with the instance and calls Dispose.
  6. Thread 2 starts using the instance, but the instance is disposed, and throws an exception.

The problem is that the application will get a reference to an instance that can be disposed.

Try to prevent doing this by redesigning your application if you can. It's a bad practice to expose service types that implement IDisposable, because IDisposable is a leaky abstraction. My personal preference is even to prevent any implementations of these services to implement IDisposable. In most scenarios a redesign can prevent you from having to do this.

If you need to use IDisposable objects, the usual way to do this is to create and inject factories that create these IDisposable objects. This way the consumer can safely dispose such an object, without any problem.

The general problem here is that it is hard to create objects that implement IDisposable, that are actually thread-safe.

If you really want this, you can try creating a decorator that does reference counting. Look for instance at the decorator below. It wraps an IService and implements IService. IService implements IDisposable. The decorator takes a Func<IService> delegate that allows creation of instances. Creation and disposal of objects is protected by a lock statement and the and the decorator counts the references to it by callers. It will dispose the object and create a new one, after the last consumer disposed the decorator.

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;
    }
}

Please note that this implementation is still flawed, because of the following reasons:

  1. Calling Dispose multiple times breaks the decorator.
  2. When consumers call the SomeOperation multiple times (or the IService has multiple methods) the implementation will break.

It is pretty hard to create a decorator that functions as expected. One simple way of doing this is by serializing access to the object, but when you do this, you probably want to use a single instance per thread. That would be much easier.

I hope this helps.

油焖大侠 2024-11-10 01:48:46

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

看看 Nate Kohari 的 缓存和收集文章介绍如何在 Ninject 2 中完成范围界定。

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

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

I know this is solved but... @Steven's answer doesnt point out that there's a InScope mechanism in Ninject that addresses aspects of what you're looking for.

Have a look at Nate Kohari's Cache and Collect article re how scoping can be done in Ninject 2.

Next, go look at the ninject source and see how InRequestScope is implemented (including how the teardown is hooked in). There's some work planned for 2.3-4 to generalise how that works to permit it to be used for some complex hosting scenarios.

When you've looked at these two references, go ask a question on the ninject mailing list and you'll definitely have a solution.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文