Unity依存関係注入コンテナーには、SynchronizedLifetimeManagerがMonitor.ExitメソッドにSynchronizationLockExceptionをスローさせ、その後キャッチして無視するという、広く知られている問題のように見えるものがあります。スローされた例外で中断するようにVisual Studioを設定してデバッグしたいので、これは私にとって問題です。そのため、アプリケーションが起動するたびに、理由もなくこの例外を何度も中断しています。
この例外がスローされないようにするにはどうすればよいですか?
この問題がWebの他の場所で言及されている場合、アドバイスには通常、デバッガの設定を変更して無視することが含まれます。これは、医者のところへ行き、「医者、医者、私が腕を上げると腕が痛い」と言って、「さあ、持ち上げないでください」と言われるのと同じです。そもそも例外がスローされないようにするソリューションを探しています。
SetValueメソッドでは例外が発生します。これは、GetValueが最初に呼び出され、Monitor.Enterが呼び出されることを前提としているためです。ただし、LifetimeStrategyクラスとUnityDefaultBehaviorExtensionクラスはどちらも、GetValueを呼び出さずに定期的にSetValueを呼び出します。
ソースコードを変更したり、独自のバージョンのUnityを維持したりする必要はないので、拡張機能、ポリシー、または戦略の組み合わせをコンテナに追加して、それが確実に行われるようにするソリューションを望んでいますライフタイムマネージャーはSynchronizedLifetimeManagerであり、GetValueは常に何よりも先に呼び出されます。
コードがSynchronizedLifetimeManager、またはContainerControlledLifetimeManagerのような子孫を呼び出す方法はたくさんあると思いますが、特に問題を引き起こしている2つのシナリオがありました。
1つ目は私自身の障害でした-コンテナーへの参照を提供するためにコンストラクターインジェクションを使用しており、そのコンストラクターで、将来使用するためにクラスの新しいインスタンスをコンテナーに追加していました。この逆方向のアプローチには、ライフタイムマネージャーをTransientからContainerControlledに変更する効果があり、GetValueを呼び出すオブジェクトUnityがSetValueを呼び出すオブジェクトと同じではありませんでした。学んだ教訓はビルド中にオブジェクトのライフタイムマネージャーを変更する可能性があるものは何もしないでください。
2番目のシナリオは、RegisterInstanceが呼び出されるたびに、UnityDefaultBehaviorExtensionが最初にGetValueを呼び出さずにSetValueを呼び出すというものでした。幸運なことに、Unityは十分に拡張可能であり、十分な流血をもって、問題を回避できます。
次のような新しい動作拡張機能から始めます。
/// <summary>
/// Replaces <see cref="UnityDefaultBehaviorExtension"/> to eliminate
/// <see cref="SynchronizationLockException"/> exceptions that would otherwise occur
/// when using <c>RegisterInstance</c>.
/// </summary>
public class UnitySafeBehaviorExtension : UnityDefaultBehaviorExtension
{
/// <summary>
/// Adds this extension's behavior to the container.
/// </summary>
protected override void Initialize()
{
Context.RegisteringInstance += PreRegisteringInstance;
base.Initialize();
}
/// <summary>
/// Handles the <see cref="ExtensionContext.RegisteringInstance"/> event by
/// ensuring that, if the lifetime manager is a
/// <see cref="SynchronizedLifetimeManager"/> that its
/// <see cref="SynchronizedLifetimeManager.GetValue"/> method has been called.
/// </summary>
/// <param name="sender">The object responsible for raising the event.</param>
/// <param name="e">A <see cref="RegisterInstanceEventArgs"/> containing the
/// event's data.</param>
private void PreRegisteringInstance(object sender, RegisterInstanceEventArgs e)
{
if (e.LifetimeManager is SynchronizedLifetimeManager)
{
e.LifetimeManager.GetValue();
}
}
}
次に、デフォルトの動作を置き換える方法が必要です。 Unityには特定の拡張機能を削除する方法がないため、すべてを削除して、他の拡張機能を再び元に戻す必要があります。
public static IUnityContainer InstallCoreExtensions(this IUnityContainer container)
{
container.RemoveAllExtensions();
container.AddExtension(new UnityClearBuildPlanStrategies());
container.AddExtension(new UnitySafeBehaviorExtension());
#pragma warning disable 612,618 // Marked as obsolete, but Unity still uses it internally.
container.AddExtension(new InjectedMembers());
#pragma warning restore 612,618
container.AddExtension(new UnityDefaultStrategiesExtension());
return container;
}
UnityClearBuildPlanStrategies
? RemoveAllExtensionsは、コンテナーのポリシーと戦略の1つを除くすべての内部リストをクリアするため、デフォルトの拡張機能を復元するときに重複を挿入しないように、別の拡張機能を使用する必要がありました。
/// <summary>
/// Implements a <see cref="UnityContainerExtension"/> that clears the list of
/// build plan strategies held by the container.
/// </summary>
public class UnityClearBuildPlanStrategies : UnityContainerExtension
{
protected override void Initialize()
{
Context.BuildPlanStrategies.Clear();
}
}
これで、狂気の瀬戸際に追い込まれることを恐れることなく、RegisterInstanceを安全に使用できます。念のため、以下にいくつかのテストを示します。
[TestClass]
public class UnitySafeBehaviorExtensionTests : ITest
{
private IUnityContainer Container;
private List<Exception> FirstChanceExceptions;
[TestInitialize]
public void TestInitialize()
{
Container = new UnityContainer();
FirstChanceExceptions = new List<Exception>();
AppDomain.CurrentDomain.FirstChanceException += FirstChanceExceptionRaised;
}
[TestCleanup]
public void TestCleanup()
{
AppDomain.CurrentDomain.FirstChanceException -= FirstChanceExceptionRaised;
}
private void FirstChanceExceptionRaised(object sender, FirstChanceExceptionEventArgs e)
{
FirstChanceExceptions.Add(e.Exception);
}
/// <summary>
/// Tests that the default behavior of <c>UnityContainer</c> leads to a <c>SynchronizationLockException</c>
/// being throw on <c>RegisterInstance</c>.
/// </summary>
[TestMethod]
public void UnityDefaultBehaviorRaisesExceptionOnRegisterInstance()
{
Container.RegisterInstance<ITest>(this);
Assert.AreEqual(1, FirstChanceExceptions.Count);
Assert.IsInstanceOfType(FirstChanceExceptions[0], typeof(SynchronizationLockException));
}
/// <summary>
/// Tests that <c>UnitySafeBehaviorExtension</c> protects against <c>SynchronizationLockException</c>s being
/// thrown during calls to <c>RegisterInstance</c>.
/// </summary>
[TestMethod]
public void SafeBehaviorPreventsExceptionOnRegisterInstance()
{
Container.RemoveAllExtensions();
Container.AddExtension(new UnitySafeBehaviorExtension());
Container.AddExtension(new InjectedMembers());
Container.AddExtension(new UnityDefaultStrategiesExtension());
Container.RegisterInstance<ITest>(this);
Assert.AreEqual(0, FirstChanceExceptions.Count);
}
}
public interface ITest { }
修正 Unityの最新リリース(2.1.505.2)。 NuGetから入手してください。
あなたの質問への答えは残念ながらノーです。私はこれをマイクロソフトのパターン&プラクティスグループの開発チームに追跡しました(私は最近まで開発リーダーでした)。これはEntLib 5.0で検討すべきバグとしてありました。調査を行ったところ、これはコードとデバッガの間の予期しない相互作用によって引き起こされたという結論に達しました。修正を検討しましたが、これは既存のコードよりも複雑であることがわかりました。結局、これは他のものよりも優先され、5の基準にはなりませんでした。
申し訳ありませんが、これ以上の答えはありません。それが慰めであるなら、私もそれを苛立たせます。
私はこの短い解決策を使用します:
/// <summary>
/// KVV 20110502
/// Fix for bug in Unity throwing a synchronizedlockexception at each register
/// </summary>
class LifeTimeManager : ContainerControlledLifetimeManager
{
protected override void SynchronizedSetValue(object newValue)
{
base.SynchronizedGetValue();
base.SynchronizedSetValue(newValue);
}
}
次のように使用します。
private UnityContainer _container;
...
_container.RegisterInstance(instance, new LifeTimeManager());
問題は、ContainerControlledLifetimeManagerの基本クラスがSynchronizedSetValueがbase.GetValueを介してmonitor.Enter()を実行することを期待していることですが、ContainerControlledLifetimeManagerクラスはこれを実行できません(明らかに、開発者が「例外でブレーク」を有効にしていませんでしたか?) 。
よろしく、公園
ロリーのソリューションは素晴らしいです-ありがとう。毎日私を困らせる問題を解決しました! Roryのソリューションにいくつかのマイナーな調整を加えて、登録されている拡張機能を処理できるようにしました(私の場合、WPF Prism/Composite拡張機能がありました)。
public static void ReplaceBehaviourExtensionsWithSafeExtension(IUnityContainer container)
{
var extensionsField = container.GetType().GetField("extensions", BindingFlags.Instance | BindingFlags.NonPublic);
var extensionsList = (List<UnityContainerExtension>)extensionsField.GetValue(container);
var existingExtensions = extensionsList.ToArray();
container.RemoveAllExtensions();
container.AddExtension(new UnitySafeBehaviorExtension());
foreach (var extension in existingExtensions)
{
if (!(extension is UnityDefaultBehaviorExtension))
{
container.AddExtension(extension);
}
}
}
Zubin Appooの答えの1つの間違いに注意してください。彼のコードにはnityClearBuildPlanStrategiesがありません。
正しいコードスニペットは次のとおりです。
FieldInfo extensionsField = container.GetType().GetField("extensions", BindingFlags.Instance | BindingFlags.NonPublic);
List<UnityContainerExtension> extensionsList = (List<UnityContainerExtension>)extensionsField.GetValue(container);
UnityContainerExtension[] existingExtensions = extensionsList.ToArray();
container.RemoveAllExtensions();
container.AddExtension(new UnityClearBuildPlanStrategiesExtension());
container.AddExtension(new UnitySafeBehaviorExtension());
foreach (UnityContainerExtension extension in existingExtensions)
{
if (!(extension is UnityDefaultBehaviorExtension))
{
container.AddExtension(extension);
}
}
スレッドセーフティの問題への対処: http://unity.codeplex.com/discussions/328841
System.Threading.SynchronizationLockExceptionのデバッグエクスペリエンスの向上: https://entlib.uservoice.com/forums/89245-general/suggestions/2377307-fix-the-system-threading-synchronizationlockexcep
タイプをロードできない場合のエラーメッセージの改善により、デバッグエクスペリエンスを改善します。 http://unity.codeplex.com/workitem/922
パブリックコンストラクターがないクラスの既存のインスタンスでBuildUp()を実行するシナリオのサポート: http://unity.codeplex.com/workitem/946
ユーザーにとって更新エクスペリエンスをできる限り単純にし、アセンブリバインディングリダイレクトの必要性を回避するために、.NETアセンブリバージョンではなく、アセンブリファイルバージョンのみをインクリメントすることを選択しました。