Почему неявный тип отношений Autofac для динамического создания экземпляров (Func <B>) соответствует областям жизни? - PullRequest
2 голосов
/ 10 октября 2019

Почему неявный тип отношений Autofac для динамической реализации относится к областям жизни?


Документы о состояниях объекта:

Область жизни -уважается с использованием этого типа отношений. Если вы зарегистрируете объект как InstancePerDependency() и несколько раз вызовете Func<B>, вы будете каждый раз получать новый экземпляр. Однако, если вы зарегистрируете объект как SingleInstance() и вызовете Func<B> для разрешения объекта более одного раза, вы будете каждый раз получать один и тот же экземпляр объекта.

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

  1. разрешение экземпляров во время выполнения без зависимости от самого Autofac
  2. разрешение нескольких экземпляров данной службы

Задержка создания экземпляра (Lazy<B>) выполняет цель # 1, что, как я понимаю, и я согласен с ее замыслом в отношении срока службы. Я не понимаю или не согласен с дизайном Autofac для Func<B>, поскольку нет способа достичь цели # 2 с помощью неявного типа отношения динамической реализации.

Чтобы обойти это и на самом деле выполнив # 2, мы вручную регистрируем Func<B>, используя тот же метод фабрики, который мы используем для регистрации B и замыкания:

void RegisterDependencies(ContainerBuilder builder)
{
    builder.Register<B>(bFactory).InstancePerLifetimeScope();
    builder.Register<Func<B>>(context => () => bFactory(context)).SingleInstance();

    B bFactory(IComponentContext context) => new B(context.Resolve<A>(), ...);
}

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


Может ли кто-нибудь пролить свет на то, почему Autofac разработал свой неявный тип отношений для динамического создания экземпляров, чтобы уважать жизненные области?

Спасибозаранее:)

1 Ответ

2 голосов
/ 11 октября 2019

Зная, что Func<B> по сути является "ярлыком" для myCurrentLifetimeScope.Resolve<B>, обернутого в объект, не относящийся к Autofac, это может помочь перевернуть этот вопрос и спросить, почему Func<B> не уважать области действия на весь срок службы и какие последствия это имело бы, если бы не .

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

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

Теперь допустим, что у вас естьсоединение с веб-службой или чем-то вроде того. Может быть, это сервис WCF. Есть некоторые вещи, которые вы можете помнить о клиентах службы WCF:

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

Учитывая, что Func<IServiceClient> отношения становятся очень интересными.

Допустим, у вас естьMVC контроллер. Это своего рода псевдокод, я не собираюсь запускать его через компилятор.

public class MyController : Controller
{
  private readonly Func<IServiceClient> clientFactory;
  public MyController(Func<IServiceClient> clientFactory)
  {
    this._clientFactory = clientFactory;
  }

  public string GetSomethingReallyCool()
  {
    var retryCount = 0;
    while(retryCount < 5)
    {
      var client = this._clientFactory();
      try
      {
        return client.GetSomethingCool();
      }
      catch
      {
        retryCount++;
      }
    }

    return "We failed to get the cool data.";
  }
}

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

Сцена установлена! Теперь, ради обсуждения, давайте представим, что отношения не учитывали прижизненные рамки, на мгновение.

Что происходит во время веб-запроса?

  • Ваш контроллер MVC разрешается из области времени жизни для каждого запроса.
  • Контроллер принимает Func<IServiceClient> функцию для создания - динамически - клиента службы WCF, когда это необходимо.
  • Действие контроллерасоздает сервисный клиент и открывает канал. Этот сервисный клиент живет в корневом контейнере , что означает, что он никогда не будет удален до тех пор, пока все приложение не будет закрыто.
  • Сбой службы. Канал неисправен, а не закрыт. Это будет просто зависать ... но вы не можете использовать один и тот же экземпляр клиента службы. Это тост.
  • Действие контроллера создает другого клиента службы и открывает канал. Этот сервисный клиент также находится в корневом контейнере и будет выделен навсегда.
  • Сервисный вызов успешно завершен, и возвращается значение. Но в то время как клиенты выходят за рамки, они одноразовые - контейнер перерабатывает отходы, верно? Таким образом, местные жители находятся вне области видимости, но они все еще выделены.

Это довольно ужасная утечка памяти .

Включите нормальное поведениеобратно! Мы закончили притворяться!

Если он подчиняется областям действия на весь срок службы, он работает следующим образом:

  • Ваш контроллер MVC разрешается из области времени действия каждого запроса.
  • Контроллер использует функцию Func<IServiceClient> для динамического создания клиента службы WCF, когда это необходимо.
  • Действие контроллера создает клиента службы и открывает канал. Этот клиент службы проживает в пределах срока действия запроса , что означает, что он будет удален после завершения запроса.
  • Ошибка службы. Канал неисправен, а не закрыт. Это будет просто зависать ... но вы не можете использовать один и тот же экземпляр клиента службы. Это тост.
  • Действие контроллера создает другого клиента службы и открывает канал. Этот сервисный клиент также находится в области запроса и будет расположен в конце запроса.
  • Сервисный вызов успешно завершен, и значение возвращается.
  • ЗапросОбласть действия времени жизни заканчивается и удаляет клиентов - закрывая каналы, освобождая ресурсы.

Нет утечки памяти, хорошая очистка.

Другой сценарий - Допустим, вы регистрировать вещи при создании областей действия времени жизни .

var builder = new ContainerBuilder();
builder.RegisterType<Alpha>();
var container = builder.Build();
using(var scope = container.BeginLifetimeScope(b => b.RegisterType<Beta>())
{
  var f = scope.Resolve<Func<Beta>>();

  // f is a Func<Beta> - call it, and you should get a B.
  // If Func<Beta> doesn't respect lifetime scopes, what
  // does this yield?
  var beta = f();
}

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

Из некоторых из них вы, вероятно, можете видеть, что единственный логический способ для Func<B> вести себя - это подчиняться жизненным границам. Это простоне работает, если это не так.

...