Unity 2.0及处理IDisposable类型(尤其是使用PerThreadLifetimeManager)

41
我知道类似的问题已经被问了多次(例如:这里这里这里这里),但这些都是针对早期版本的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;
    }
}
因此,释放容器不会释放使用此生存期管理器创建的可处理实例。线程完成也不会释放这些实例。那么谁负责释放实例?
我尝试在代码中手动处理解析的实例,并且我发现另一个问题。我无法清除该实例。Lifetime Manager的RemoveValue为空 - 一旦创建了实例,就无法将其从线程静态字典中删除(我还怀疑TearDown方法什么都不做)。因此,如果在释放实例后调用Resolve,则会获得已处理的实例。我认为,在使用此生命周期管理器与线程池中的线程时,这可能是相当大的问题。
如何正确使用此生命周期管理器?
此外,这种实现经常在自定义生命周期管理器中重复使用,例如PerCallContext、PerHttpRequest、PerAspNetSession、PerWcfCall等。只替换了线程静态字典中的某些其他结构。
此外,我是否正确理解处理可处理对象取决于生命周期管理器?因此,应用程序代码依赖于所使用的生命周期管理器。
我读到在其他IoC容器中,临时处理可处理对象是由子容器处理的,但我没有找到Unity的示例 - 它可能可以通过本地范围的子容器和HiearchicalLifetimeManager来处理,但我不确定如何做到这一点。

5
通过实现自定义生命周期,可以使Unity决定性地实现处理行为,但这里解释起来太复杂。在我的书中,我用了超过六页的篇幅来解释如何实现:http://affiliate.manning.com/idevaffiliate.php?id=1150_236 - Mark Seemann
@Mark:不幸的是,你的书将在八月份出版,而我不喜欢 MEAP。 - Ladislav Mrnka
1
请查看此链接:http://drewdotnet.blogspot.com/2009/07/unity-lifetime-managers-and-wcf.html - skjagini
1
另一个有趣的点(或设计疏忽)是,在自身内部注册UnityContainer将在您处理它时抛出StackOverflowException。您需要使用ExternallyControlledLifetimeManager来防止容器尝试调用其自己的Dispose方法。 - Richard Dingwall
@RichardDingwall 你其实不需要注册容器本身,因为默认情况下它已经可以解析 IUnityContainer 接口了。 - BrainSlugs83
4个回答

6

2
最近我在将Unity集成到我的应用程序中时遇到了这个问题。在Stack Overflow和其他在线资源中找到的解决方案似乎无法令人满意地解决这个问题。
当不使用Unity时,IDisposable实例有一个被广泛理解的使用模式: 1. 在作用域小于函数的范围内,将它们放入using块中以获得"免费"的处理。 2. 当为类的实例成员创建时,在类中实现IDisposable并将清理放入Dispose()中。 3. 当传递到类的构造函数中时,不做任何操作,因为IDisposable实例是由其他地方拥有的。
Unity让事情变得混乱,因为当依赖注入正确执行时,上述第二种情况就消失了。所有依赖项都应该被注入,这意味着基本上没有类会拥有正在创建的IDisposable实例。然而,它也没有提供一种方法来"获取"在Resolve()调用期间创建的IDisposable实例,因此似乎不能使用using块。还有什么选择吗?
我的结论是,Resolve() 接口本质上是错误的。仅返回请求的类型并泄漏需要特殊处理的对象(如 IDisposable)是不正确的。
作为回应,我为 Unity 写了 IDisposableTrackingExtension 扩展,它跟踪在类型解析期间创建的 IDisposable 实例,并返回一个可释放的包装对象,其中包含所请求的类型的实例和对象图中的所有 IDisposable 依赖项。
使用此扩展,类型解析如下所示(此处使用工厂,因为您的业务类不应将 IUnityContainer 作为依赖项):
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,因此它们不会对它们进行处理。执行类型解析(通过工厂)以获取动态创建对象的类是所有者,而这个扩展提供了管理处理范围的功能。

2
看 Unity 2.0 的源代码,似乎 LifetimeManagers 用于以不同方式来保持对象的范围,以便垃圾回收器不会清除它们。例如,对于 PerThreadLifetimeManager,它将使用 ThreadStatic 来保留具有特定线程生命周期的每个对象的引用。然而,在容器被释放之前,它不会调用 Dispose。
编辑:经过仔细检查,LifetimeContainer 只包含 LifetimeManagers(因此命名为 "Lifetime"Container)。因此,当其被释放时,它只会处理生命周期管理器。(这是已经讨论过的问题)。

1
根据我的调查,只有ContainerControlled和Hierarchical生命周期管理器会Dispose已解析的实例:http://www.ladislavmrnka.com/2011/03/unity-build-in-lifetime-managers/ - Ladislav Mrnka
@Ladislav 你是对的... 我进一步查看了代码并意识到我对LifetimeManager实际上包含什么有一个错误的假设。 - Garo Yeriazarian

1

使用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();
}

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接