Подтвердить что ты не робот

Unity 2.0 и обработка IDisposable типов (особенно с PerThreadLifetimeManager)

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

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

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

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

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

4b9b3361

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

Недавно я столкнулся с этой проблемой, поскольку я был инструментом Unity в своем приложении. Решения, которые я нашел здесь на Qaru и в других местах в Интернете, по-моему, не удовлетворили проблему, по моему мнению.

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

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

  • При создании для члена экземпляра класса реализуйте IDisposable в классе и очищайте его в Dispose().

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

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

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

В ответ я написал расширение IDisposableTrackingExtension для Unity, которое отслеживает IDisposable экземпляры, созданные во время разрешения типа, и возвращает одноразовую обертку объект, содержащий экземпляр запрошенного типа и всех зависимых от IDisposable зависимостей от графа объектов.

С этим расширением разрешение типа выглядит так (показано здесь с помощью factory, так как ваши бизнес-классы никогда не должны принимать 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
    }
  }
}

Это примиряет шаблоны использования IDsposable # 1 и # 3 сверху. Нормальные классы используют инъекцию зависимостей; они не владеют инъекционными IDisposables, поэтому они не избавляются от них. Классы, которые выполняют разрешение по типу (через фабрики), потому что им нужны динамически созданные объекты, эти классы являются владельцами, и это расширение предоставляет средство для управления областями удаления.