Можно ли сделать так, чтобы Unity не генерировала исключение SynchronizationLockException все время? - PullRequest
64 голосов
/ 20 мая 2010

В контейнере внедрения зависимостей Unity есть, как представляется, широко известная проблема, из-за которой SynchronizedLifetimeManager часто вызывает метод Monitor.Exit для создания исключения SynchronizationLockException, которое затем перехватывается и игнорируется. Это проблема для меня, потому что мне нравится отлаживать с Visual Studio, настроенным на разрыв при любом выданном исключении, поэтому каждый раз, когда мое приложение запускается, я разрываю это исключение несколько раз без причины.

Как я могу предотвратить появление этого исключения?

Везде, где эта проблема упоминается где-либо еще в Интернете, совет обычно включает изменение настроек отладчика, чтобы игнорировать ее. Это похоже на то, чтобы пойти к доктору и сказать: «Доктор, доктор, у меня болит рука, когда я поднимаю ее», чтобы сказать: «Ну, хватит поднимать ее». Я ищу решение, которое остановит исключение.

Исключение возникает в методе SetValue, поскольку он предполагает, что сначала будет вызван GetValue, где вызывается Monitor.Enter. Однако классы LifetimeStrategy и UnityDefaultBehaviorExtension оба регулярно вызывают SetValue без вызова GetValue.

Я бы предпочел не менять исходный код и поддерживать свою собственную версию Unity, поэтому я надеюсь на решение, в котором я могу добавить некоторую комбинацию расширений, политик или стратегий в контейнер, который обеспечит если менеджер времени жизни - SynchronizedLifetimeManager, GetValue всегда вызывается раньше всего.

Ответы [ 8 ]

38 голосов
/ 01 июля 2010

Я уверен, что есть много способов, которыми код мог бы вызвать 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 { }
12 голосов
/ 07 августа 2012

Исправлено в последней версии Unity (2.1.505.2). Получите это через NuGet.

10 голосов
/ 30 июня 2010

Ответ на ваш вопрос, к сожалению, нет. Я следил за этим с командой разработчиков здесь, в группе шаблонов и практик Microsoft (до недавнего времени я был там руководителем разработки), и у нас это было ошибкой для рассмотрения в EntLib 5.0. Мы провели некоторое исследование и пришли к выводу, что это было вызвано неожиданным взаимодействием между нашим кодом и отладчиком. Мы рассматривали исправление, но оно оказалось более сложным, чем существующий код. В конце концов, это было расставлено по приоритетам ниже других и не составило планку на 5.

Извините, у меня нет лучшего ответа для вас. Если это утешит, то и меня это раздражает.

7 голосов
/ 03 мая 2011

Я использую это короткое решение:

/// <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 выполнит монитор. включен?).

С уважением, Koen

4 голосов
/ 21 октября 2010

Решение Рори великолепно - спасибо. Решил проблему, которая раздражает меня каждый день! Я сделал несколько небольших изменений в решении Рори, чтобы оно обрабатывало любые зарегистрированные расширения (в моем случае у меня было расширение 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);
            }
        }
    }
1 голос
/ 29 декабря 2010

Остерегайтесь одной ошибки в ответе Зубина Аппу: в его коде отсутствует UnityClearBuildPlanStrategies .

Правильный фрагмент кода:

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);
   }
}
0 голосов
/ 10 января 2014

Unity 2.1 - обновление за август 2012 года исправляет ошибку

  1. Решение проблемы безопасности потока: http://unity.codeplex.com/discussions/328841

  2. Улучшение отладки в System.Threading.SynchronizationLockException: https://entlib.uservoice.com/forums/89245-general/suggestions/2377307-fix-the-system-threading-synchronizationlockexcep

  3. Улучшение отладки за счет улучшения обмена сообщениями об ошибках, когда тип не может быть загружен: http://unity.codeplex.com/workitem/9223

  4. Поддержка сценария выполнения BuildUp () для существующего экземпляра класса, у которого нет открытого конструктора: http://unity.codeplex.com/workitem/9460

Чтобы сделать процесс обновления максимально простым для пользователей и избежать необходимости перенаправления привязки сборки, мы решили увеличивать только версию файла сборки, а не версию сборки .NET.

0 голосов
/ 15 июня 2010

Это может помочь вам:

  • Перейти к отладке -> Исключения ...
  • Найдите исключения, которые вас очень расстроили, SynchronizationLockException

Вуаля.

...