Unity 2.0 和处理 IDisposable 类型(特别是使用 PerThreadLifetimeManager)

发布于 2024-10-19 12:39:51 字数 2635 浏览 6 评论 0原文

我知道类似的问题已被问过多次(例如:此处此处此处此处 )但对于以前版本的 Unity,答案取决于使用的 LifetimeManager 类。

文档说:

Unity 使用继承的特定类型 来自 LifetimeManager 基类 (统称为生命周期 经理)来控制它的存储方式 对对象实例的引用以及如何 容器处理这些 实例。

好的,听起来不错,所以我决定检查生命周期管理器中构建的实现。我的结论:

  • TransientLifetimeManager - 不处理处置。容器仅解析实例而不跟踪它。调用代码负责处置实例。
  • ContainerControlledLifetimeManager - 在释放生命周期管理器时(=释放容器时)释放实例。提供在层次结构中的所有容器之间共享的单例实例。
  • HierarchicalLifetimeManager - 从ContainerControlledLifetimeManager派生行为。它为层次结构(子容器)中的每个容器提供“单例”实例。
  • ExternallyControlledLifetimeManager - 不处理处置。正确的行为,因为容器不是实例的所有者。
  • PerResolveLifetimeManager - 不处理处置。它通常与 TransientLifetimeManager 相同,但它允许在解析整个对象图时重用实例进行依赖注入。
  • PerThreadLifetimeManager - 不处理处置,如 MSDN 中所述。 谁负责处置?

内置PerThreadLifetimeManager的实现是:

public class PerThreadLifetimeManager : LifetimeManager
{
    private readonly Guid key = Guid.NewGuid();
    [ThreadStatic]
    private static Dictionary<Guid, object> values;

    private static void EnsureValues()
    {
        if (values == null)
        {
            values = new Dictionary<Guid, object>();
        }
    }

    public override object GetValue()
    {
        object result;
        EnsureValues();
        values.TryGetValue(this.key, out result);
        return result;
    }

    public override void RemoveValue()
    { }

    public override void SetValue(object newValue)
    {
        EnsureValues();
        values[this.key] = newValue;
    }
}

因此处置容器不会处置使用此生命周期管理器创建的一次性实例。线程完成也不会处置这些实例。那么谁负责释放实例呢?

我尝试在代码中手动处理已解析的实例,但发现了另一个问题。我无法拆解该实例。生命周期管理器的RemoveValue为空 - 一旦创建实例,就不可能将其从线程静态字典中删除(我也怀疑 TearDown 方法什么也不做)。因此,如果您在处置实例后调用Resolve,您将获得已处置的实例。我认为当将此生命周期管理器与线程池中的线程一起使用时,这可能是一个很大的问题。

如何正确使用这个生命周期管理器?

此外,此实现经常在自定义生命周期管理器中重用,例如 PerCallContext、PerHttpRequest、PerAspNetSession、PerWcfCall 等。只有线程静态字典被替换为其他一些构造。

另外,我是否正确理解处理一次性对象取决于生命周期管理器?因此应用程序代码依赖于使用的生命周期管理器。

我读到,在其他 IoC 容器中,处理临时一次性对象是由子容器处理的,但我没有找到 Unity 的示例 - 它可能可以使用本地作用域子容器和 HiearchicalLifetimeManager 来处理,但我不确定怎么做。

I know that similar question was asked several times (for example: here, here,here and here) but it was for previous versions of Unity where the answer was dependent on used LifetimeManager class.

Documentation says:

Unity uses specific types that inherit
from the LifetimeManager base class
(collectively referred to as lifetime
managers) to control how it stores
references to object instances and how
the container disposes of these
instances.

Ok, sounds good so I decided to check implementation of build in lifetime managers. My conclusion:

  • TransientLifetimeManager - no handling of disposing. Container only resolves instance and it does not track it. Calling code is responsible for disposing instance.
  • ContainerControlledLifetimeManager - disposes instance when lifetime manager is disposed (= when container is disposed). Provides singleton instance shared among all containers in hiearchy.
  • HierarchicalLifetimeManager - derives behavior from ContainerControlledLifetimeManager. It provides "singleton" instance per container in hiearchy (subcontainers).
  • ExternallyControlledLifetimeManager - no handling of disposing. Correct behavior because container is not owner of the instance.
  • PerResolveLifetimeManager - no handling of disposing. It is generally same as TransientLifetimeManager but it allows reusing instance for dependency injection when resolving whole object graph.
  • PerThreadLifetimeManager - no handling of disposing as also described in MSDN. Who is responsible for disposing?

Implementation of build-in PerThreadLifetimeManager is:

public class PerThreadLifetimeManager : LifetimeManager
{
    private readonly Guid key = Guid.NewGuid();
    [ThreadStatic]
    private static Dictionary<Guid, object> values;

    private static void EnsureValues()
    {
        if (values == null)
        {
            values = new Dictionary<Guid, object>();
        }
    }

    public override object GetValue()
    {
        object result;
        EnsureValues();
        values.TryGetValue(this.key, out result);
        return result;
    }

    public override void RemoveValue()
    { }

    public override void SetValue(object newValue)
    {
        EnsureValues();
        values[this.key] = newValue;
    }
}

So disposing container does not dispose disposable instances created with this lifetime manager. Thread completion will also not dispose those instances. So who is responsible for releasing instances?

I tried to manually dispose resolved instance in code and I found another problem. I can't teardown the instnace. RemoveValue of lifetime manager is empty - once the instance is created it is not possible to remove it from thread static dictionary (I'm also suspicious that TearDown method does nothing). So if you call Resolve after disposing the instance you will get disposed instance. I think this can be quite big problem when using this lifetime manager with threads from thread pool.

How to correctly use this lifetime manager?

Moreover this implementation is often reused in custom lifetime managers like PerCallContext, PerHttpRequest, PerAspNetSession, PerWcfCall, etc. Only thread static dictionary is replaced with some other construct.

Also do I understand it correctly that handling disposable objects is dependent on lifetime manager? So the application code is dependent on used lifetime manager.

I read that in other IoC containers dealing with temporary disposable objects is handled by subcontainers but I didn't find example for Unity - it could be probably handled with local scoped subcontainer and HiearchicalLifetimeManager but I'm not sure how to do it.

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

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

发布评论

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

评论(4

静若繁花 2024-10-26 12:39:51

只有少数情况下 Unity 才会处置实例。确实是不支持了。我的解决方案是一个自定义扩展来实现此目的 - http://www.neovolve.com/2010/06/18/unity-extension-for-disusing-build-trees-on-teardown/

There are only a few circumstances where Unity will dispose an instance. It is really unsupported. My solution was a custom extension to achieve this - http://www.neovolve.com/2010/06/18/unity-extension-for-disposing-build-trees-on-teardown/

荒人说梦 2024-10-26 12:39:51

查看 Unity 2.0 源代码,感觉 LifetimeManager 被用来以不同的方式将对象保留在范围内,这样垃圾收集器就不会删除它们。例如,对于 PerThreadLifetimeManager,它将使用 ThreadStatic 来保存具有该特定线程生命周期的每个对象的引用。但是,在容器被 Dispose 之前,它不会调用 Dispose。

有一个 LifetimeContainer 对象,用于保存所有创建的实例,然后在 UnityContainer 被 Dispose 时被 Dispose(反过来,按时间倒序 Dispose 中的所有 IDisposable)。

编辑:经过仔细检查,LifetimeContainer 仅包含 LifetimeManager(因此称为“Lifetime”Container)。因此,当它被处置时,它只处置生命周期管理器。 (我们面临已经讨论过的问题)。

Looking at the Unity 2.0 source code, it smells like the LifetimeManagers are used to keep objects in scope in different ways so the garbage collector doesn't get rid of them. For example, with the PerThreadLifetimeManager, it will use the ThreadStatic to hold a reference on each object with that particular thread's lifetime. However, it won't call Dispose until the container is Disposed.

There is a LifetimeContainer object that is used to hold onto all the instances that are created, then is Disposed when the UnityContainer is Disposed (which, in turn, Disposes all the IDisposables in there in reverse chronological order).

EDIT: upon closer inspection, the LifetimeContainer only contains LifetimeManagers (hence the name "Lifetime"Container). So when it is Disposed, it only disposes the lifetime managers. (and we face the problem that is discussed already).

痕至 2024-10-26 12:39:51

最近,当我在应用程序中使用 Unity 时,我自己也遇到了这个问题。在我看来,我在 Stack Overflow 和其他在线地方找到的解决方案似乎并没有以令人满意的方式解决这个问题。

不使用 Unity 时,IDisposable 实例具有易于理解的使用模式:

  1. 在小于函数的范围内,将它们放入 using 块中以“免费”处置。

  2. 当为类的实例成员创建时,在类中实现 IDisposable 并在 Dispose() 中进行清理。

  3. 当传递到类的构造函数时,不执行任何操作,因为 IDisposable 实例在其他地方拥有。

Unity 会让事情变得混乱,因为当依赖注入正确完成时,上面的情况 #2 就会消失。所有依赖项都应该被注入,这意味着基本上没有类拥有正在创建的 IDisposable 实例的所有权。但是,它也没有提供一种方法来“获取”在 Resolve() 调用期间创建的 IDisposables,因此似乎无法使用 using 块。还剩下什么选择?

我的结论是,Resolve() 接口本质上是错误的。仅返回请求的类型和泄漏需要特殊处理(如 IDisposable)的对象是不正确的。

作为回应,我为 Unity 编写了 IDisposableTrackingExtension 扩展,它跟踪在类型解析期间创建的 IDisposable 实例,并返回一个一次性包装对象,其中包含所请求类型的实例以及对象图中的所有 IDisposable 依赖项。

使用此扩展,类型解析看起来像这样(此处使用工厂显示,因为您的业务类永远不应该将 IUnityContainer 作为
dependency):

public class SomeTypeFactory
{
  // ... take IUnityContainer as a dependency and save it

  IDependencyDisposer< SomeType > Create()
  {
    return this.unity.ResolveForDisposal< SomeType >();
  }
}

public class BusinessClass
{
  // ... take SomeTypeFactory as a dependency and save it

  public void AfunctionThatCreatesSomeTypeDynamically()
  {
    using ( var wrapper = this.someTypeFactory.Create() )
    {
      SomeType subject = wrapper.Subject;
      // ... do stuff
    }
  }
}

这协调了上面的 IDisposable 使用模式 #1 和 #3。普通类使用依赖注入;他们不拥有注入的 IDisposables,因此他们不会处置它们。执行类型解析(通过工厂)的类,因为它们需要动态创建的对象,这些类是所有者,并且此扩展提供了管理处置范围的设施。

I came across this issue recently myself as I was instrumenting Unity into my application. The solutions I found here on Stack Overflow and elsewhere online didn't seem to address the issue in a satisfactory way, in my opinion.

When not using Unity, IDisposable instances have a well-understood usage pattern:

  1. Within a scope smaller than a function, put them in a using block to get disposal "for free".

  2. When created for an instance member of a class, implement IDisposable in the class and put clean-up in Dispose().

  3. When passed into a class's constructor, do nothing as the IDisposable instance is owned somewhere else.

Unity confuses things because when dependency injection is done properly, case #2 above goes away. All dependencies should be injected, which means essentially no classes will have ownership of the IDisposable instances being created. However, neither does it provide a way to "get at" the IDisposables that were created during a Resolve() call, so it seems that using blocks can't be used. What option is left?

My conclusion is that the Resolve() interface is essentially wrong. Returning only the requested type and leaking objects that need special handling like IDisposable can't be correct.

In response, I wrote the IDisposableTrackingExtension extension for Unity, which tracks IDisposable instances created during a type resolution, and returns a disposable wrapper object containing an instance of the requested type and all of the IDisposable dependencies from the object graph.

With this extension, type resolution looks like this (shown here using a factory, as your business classes should never take IUnityContainer as a
dependency):

public class SomeTypeFactory
{
  // ... take IUnityContainer as a dependency and save it

  IDependencyDisposer< SomeType > Create()
  {
    return this.unity.ResolveForDisposal< SomeType >();
  }
}

public class BusinessClass
{
  // ... take SomeTypeFactory as a dependency and save it

  public void AfunctionThatCreatesSomeTypeDynamically()
  {
    using ( var wrapper = this.someTypeFactory.Create() )
    {
      SomeType subject = wrapper.Subject;
      // ... do stuff
    }
  }
}

This reconciles IDisposable usage patterns #1 and #3 from above. Normal classes use dependency injection; they don't own injected IDisposables, so they don't dispose of them. Classes that perform type resolution (through factories) because they need dynamically created objects, those classes are the owners, and this extension provides the facility for managing disposal scopes.

那一片橙海, 2024-10-26 12:39:51

使用 HttpContext.Current.ApplicationInstance.EndRequest 事件挂钩到请求的末尾,然后处理存储在此生命周期管理器中的对象是否是一个可行的解决方案?像这样:

public HttpContextLifetimeManager()
{
    HttpContext.Current.ApplicationInstance.EndRequest += (sender, e) => {
        Dispose();
    };
}

public override void RemoveValue()
{
    var value = GetValue();
    IDisposable disposableValue = value as IDisposable;

    if (disposableValue != null) {
        disposableValue.Dispose();
    }
    HttpContext.Current.Items.Remove(ItemName);
}

public void Dispose()
{
    RemoveValue();
}

您不必像其他解决方案一样使用子容器,并且用于处置对象的代码仍然在生命周期管理器中,就像它应该的那样。

would it be a viable solution to use the HttpContext.Current.ApplicationInstance.EndRequest event to hook to the end of the request and then disposing of the object stored in this lifetime manager? like so:

public HttpContextLifetimeManager()
{
    HttpContext.Current.ApplicationInstance.EndRequest += (sender, e) => {
        Dispose();
    };
}

public override void RemoveValue()
{
    var value = GetValue();
    IDisposable disposableValue = value as IDisposable;

    if (disposableValue != null) {
        disposableValue.Dispose();
    }
    HttpContext.Current.Items.Remove(ItemName);
}

public void Dispose()
{
    RemoveValue();
}

you don't have to use a child container like the other solution and the code used to dispose the objects is still in the lifetime manager like it should.

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