Контейнер внедрения зависимости Единицы имеет то, что, кажется, широко известная проблема, где SynchronizedLifetimeManager будет часто вызывать Монитор. Метод выхода для броска SynchronizationLockException, который затем пойман и проигнорирован. Это - проблема для меня, потому что мне нравится отлаживать с набором Visual Studio для повреждения на любой вызванной исключительной ситуации, таким образом, каждый раз мое приложение запускает, я повреждаюсь на этом исключении многократно ни по какой причине.
Как я могу препятствовать тому, чтобы это исключение было брошено?
Везде, где это выходит, упоминается в другом месте в сети, совет обычно включает изменение настроек отладчика для игнорирования ее. Это сродни движению к доктору и высказыванию, "Доктор, Доктор, моя рука болит, когда я повышаю его", быть сказанным, "Ну, остановка повысить его". Я ищу решение, которое останавливает исключение, выданное во-первых.
Исключение происходит в методе SetValue, потому что это делает предположение, что GetValue назовут первым, где Монитор. Войдите назван. Однако классы LifetimeStrategy и UnityDefaultBehaviorExtension оба регулярно называют SetValue, не называя GetValue.
Я не должен изменить исходный код и поддержать мою собственную версию Единицы, таким образом, я надеюсь на решение, где я могу добавить некоторую комбинацию расширений, политик или стратегий к контейнеру, который гарантирует, что, если пожизненным менеджером является SynchronizedLifetimeManager, GetValue всегда называют перед чем-либо еще.
Я уверен, что существует множество способов вызова SynchronizedLifetimeManager или его потомка ContainerControlledLifetimeManager, но было два сценария, которые вызывали у меня проблемы.
Первый был моей собственной ошибкой - я использовал инъекцию конструктора для предоставления ссылки на контейнер, и в этом конструкторе я также добавлял новый экземпляр класса в контейнер для будущего использования. Этот обратный подход привел к изменению менеджера времени жизни с Transient на ContainerControlled, так что объект, для которого Unity вызывала GetValue, не был тем же самым объектом, для которого она вызывала SetValue. Выученный урок: не делайте ничего во время сборки, что может изменить менеджер времени жизни объекта.
Второй сценарий заключался в том, что каждый раз при вызове RegisterInstance расширение UnityDefaultBehaviorExtension вызывает SetValue без предварительного вызова GetValue. К счастью, 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 очищает все внутренние списки политик и стратегий контейнера, кроме одной, поэтому мне пришлось использовать другое расширение, чтобы избежать вставки дубликатов при восстановлении расширений по умолчанию:
/// <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 { }
Это может вам помочь:
Вуаля.
К сожалению, ответ на ваш вопрос отрицательный. Я обсудил это с командой разработчиков здесь, в группе шаблонов и практик Microsoft (до недавнего времени я был руководителем разработчика), и у нас возникла ошибка, которую нужно учитывать в EntLib 5.0. Мы провели небольшое расследование и пришли к выводу, что это было вызвано неожиданными взаимодействиями между нашим кодом и отладчиком. Мы рассматривали исправление, но оно оказалось сложнее, чем существующий код. В конце концов, это стало приоритетом ниже других вещей и не дало планку на 5.
Извините, у меня нет лучшего ответа для вас. Если это хоть какое-то утешение, меня это тоже раздражает.