Unity 2.0 и обработка IDisposable типов (особенно с PerThreadLifetimeManager) - PullRequest
40 голосов
/ 27 февраля 2011

Я знаю, что подобный вопрос задавался несколько раз (например: здесь , здесь , здесь и здесь ), но это был для предыдущих версий Unity, где ответ зависел от используемого LifetimeManager класса.

Документация гласит:

Unity использует определенные типы, которые наследуют из базового класса LifetimeManager (в совокупности упоминается как пожизненная менеджеры) контролировать, как он хранит ссылки на экземпляры объектов и как контейнер избавляется от этих экземпляры.

Хорошо, звучит хорошо, поэтому я решил проверить реализацию встроенных менеджеров жизни. Мой вывод:

  • TransientLifetimeManager - без утилизации. Контейнер разрешает только экземпляр и не отслеживает его. Телефонный код отвечает за удаление экземпляра.
  • ContainerControlledLifetimeManager - удаляет экземпляр, когда диспетчер времени жизни удаляется (= когда контейнер утилизируется). Предоставляет единичный экземпляр, общий для всех контейнеров в hiearchy.
  • HierarchicalLifetimeManager - выводит поведение из ContainerControlledLifetimeManager. Он предоставляет «одиночный» экземпляр для каждого контейнера в hiearchy (подконтейнеры).
  • 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, но я не уверен, как сделай это.

Ответы [ 4 ]

5 голосов
/ 12 октября 2011

Есть только несколько обстоятельств, когда Unity будет располагать экземпляром. Это действительно не поддерживается. Моим решением было специальное расширение для достижения этой цели - http://www.neovolve.com/2010/06/18/unity-extension-for-disposing-build-trees-on-teardown/

2 голосов
/ 27 апреля 2011

Глядя на исходный код Unity 2.0, он пахнет так, как будто LifetimeManager используется различными способами для сохранения объектов в области видимости, чтобы сборщик мусора не избавился от них.Например, с PerThreadLifetimeManager он будет использовать ThreadStatic для хранения ссылки на каждый объект с временем жизни этого конкретного потока.Однако он не будет вызывать Dispose до тех пор, пока контейнер не будет удален.

Существует объект LifetimeContainer, который используется для хранения всех созданных экземпляров, затем он удаляется при удалении UnityContainer (который, в свою очередь, удаляет все IDisposables там в обратном хронологическом порядке).

EDIT: при ближайшем рассмотрении LifetimeContainer содержит только LifetimeManagers (отсюда и название «Lifetime» Container).Таким образом, когда он удаляется, он только распоряжается пожизненными менеджерами.(и мы сталкиваемся с проблемой, которая уже обсуждается).

1 голос
/ 12 октября 2011

Было бы жизнеспособным решением использовать событие 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();
}

вам не нужно использовать дочерний контейнер, как другое решение, а код, используемый для удаления объектов, все еще находится в менеджере времени жизни, как и должен.

0 голосов
/ 25 апреля 2016

Я недавно столкнулся с этой проблемой, когда внедрял Unity в свое приложение.По моему мнению, решения, которые я нашел здесь в Stack Overflow и в других местах в Интернете, не решали проблему удовлетворительным образом.

Когда экземпляры IDisposable не используются, у них есть хорошо понятный шаблон использования:

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

  2. При создании длячлен экземпляра класса, реализовать IDisposable в классе и поместить очистку в Dispose().

  3. При передаче в конструктор класса ничего не делать, поскольку экземпляр IDisposable где-то принадлежитelse.

Unity запутывает вещи, потому что, когда внедрение зависимостей сделано правильно, приведенный выше случай №2 исчезает.Все зависимости должны быть внедрены, что означает, что по существу ни один класс не будет владеть создаваемыми экземплярами IDisposable.Тем не менее, он также не обеспечивает способ "получить" IDisposables, которые были созданы во время вызова Resolve(), поэтому кажется, что блоки using не могут быть использованы.Какой вариант остался?

Мой вывод таков, что интерфейс Resolve() по сути неправильный.Возврат только запрошенного типа и утечка объектов, которые нуждаются в специальной обработке, такой как IDisposable, не может быть правильным.

В ответ я написал расширение IDisposableTrackingExtension для Unity, которое отслеживает экземпляры 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 сверху.Обычные классы используют внедрение зависимостей;им не принадлежат инъецированные IDisposables, поэтому они не избавляются от них.Классы, которые выполняют разрешение типов (через фабрики), поскольку им нужны динамически создаваемые объекты, эти классы являются владельцами, и это расширение предоставляет возможность управления областями удаления.

...