web-dev-qa-db-ja.com

Unity 2.0とIDisposableタイプの処理(特にPerThreadLifetimeManagerを使用)

同様の質問が何度か行われたことは知っています(例: ここここここ および ここ )が答えが使用されたLifetimeManagerクラスに依存していた以前のバージョンのUnity用でした。

ドキュメントによると:

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で処理できますが、その方法がわかりません。やれ。

40
Ladislav Mrnka

Unityがインスタンスを破棄する状況はごくわずかです。それは本当にサポートされていません。私のソリューションは、これを実現するためのカスタム拡張機能でした http://www.neovolve.com/2010/06/18/unity-extension-for-disposed-build-trees-on-teardown/

6
Rory Primrose

Unity 2.0のソースコードを見ると、LifetimeManagersを使用してオブジェクトをさまざまな方法でスコープ内に保持しているため、ガベージコレクターがオブジェクトを削除しないように思われます。たとえば、PerThreadLifetimeManagerでは、ThreadStaticを使用して、その特定のスレッドの存続期間を持つ各オブジェクトの参照を保持します。ただし、コンテナが破棄されるまで、破棄は呼び出されません。

作成されたすべてのインスタンスを保持するために使用されるLifetimeContainerオブジェクトがあり、UnityContainerが破棄されると破棄されます(これにより、すべてのIDisposableが新しい順に破棄されます)。

編集:詳しく調べると、LifetimeContainerにはLifetimeManagerのみが含まれています(したがって、「Lifetime」Containerという名前が付けられています)。したがって、廃棄される場合、ライフタイムマネージャーのみが廃棄されます。 (そして、すでに説明した問題に直面しています)。

2

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

他のソリューションのように子コンテナーを使用する必要はなく、オブジェクトの破棄に使用されるコードは、本来のようにライフタイムマネージャーに残ります。

1
Francois Joly

Unityをアプリケーションに組み込んでいるときに、最近この問題に遭遇しました。私の意見では、Stack Overflowや他のオンラインで見つけた解決策は、満足のいく方法で問題に対処していないようでした。

Unityを使用していない場合、IDisposableインスタンスにはよく理解されている使用パターンがあります。

  1. 関数よりも小さいスコープ内で、それらをusingブロックに入れて、「無料」で処分します。

  2. クラスのインスタンスメンバー用に作成する場合は、クラスにIDisposableを実装し、Dispose()にクリーンアップを配置します。

  3. IDisposableインスタンスは別の場所に所有されているため、クラスのコンストラクターに渡されるときは何もしません。

Unityは、依存性注入が適切に行われると、上記のケース2がなくなるため、混乱を招きます。すべての依存関係を注入する必要があります。つまり、作成される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が上から調整されます。通常のクラスは依存性注入を使用します。彼らは注入されたIDisposableを所有していないので、それらを処分しません。動的に作成されたオブジェクトを必要とするために(ファクトリを介して)型解決を実行するクラス。これらのクラスは所有者であり、この拡張機能は廃棄スコープを管理するための機能を提供します。

1
Kurt Hutchinson