Можно ли сделать Unity, чтобы не бросать SynchronizationLockException все время?


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

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

везде, где эта проблема упоминается в другом месте в интернете, совет обычно включает в себя изменение параметров отладчика, чтобы игнорировать его. Это сродни тому, чтобы пойти к врачу и сказать: "Доктор, доктор, у меня болит рука, когда я поднимаю ее", чтобы мне сказали: "ну, перестаньте поднимать ее."Я ищу решение, которое в первую очередь останавливает исключение.

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

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

8 64

8 ответов:

Я уверен, что есть много способов код может вызвать SynchronizedLifetimeManager, или потомок, как ContainerControlledLifetimeManager, но было два сценария, в частности, которые вызывали у меня проблемы.

первая была моей собственной ошибкой-я использовал инъекцию конструктора, чтобы предоставить ссылку на контейнер, и в этом конструкторе я также добавлял новый экземпляр класса в контейнер для будущего использования. Назад этот подход меняется диспетчер времени жизни от Переходного до ContainerControlled так, чтобы объект Unity с именем GetValue on не был тем же объектом, который он называл SetValue on. Извлеченный урок заключается не делайте во время сборки ничего, что могло бы изменить диспетчер времени жизни объекта.

второй сценарий состоял в том, что каждый раз, когда вызывается 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 { }

основные в последнем выпуске Unity (2.1.505.2). Получить его через NuGet.

ответ на ваш вопрос, к сожалению, нет. Я следил за этим с командой разработчиков здесь, в группе Microsoft patterns & practices (до недавнего времени я был ведущим разработчиком), и у нас было это как ошибка для 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 будет выполнять монитор.Введите () через базу.GetValue, однако класс ContainerControlledLifetimeManager не может этого сделать (по-видимому, его разработчики не включили "break at exception"?).

С уважением, Коен

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

остерегайтесь одной ошибки в ответе Зубина Аппу: есть 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);
   }
}

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

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

  2. улучшение опыта отладки в системе.Нарезка резьбы.SynchronizationLockException: https://entlib.uservoice.com/forums/89245-general/suggestions/2377307-fix-the-system-threading-synchronizationlockexcep

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

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

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

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

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

вуаля.