#c# #autofac
Вопрос:
Много раз я создаю дочернюю область жизни в Autofac просто для замены или дополнения регистраций в родительской. И с этого момента я использую только дочернюю область жизни. В документации autofac указано, что дочерние области не удаляются автоматически:
Дочерние области НЕ удаляются автоматически
Хотя сами области жизненного цикла реализуются
IDisposable
, созданные вами области жизненного цикла не удаляются автоматически для вас. Если вы создаете область жизненного цикла, вы несете ответственность за ее вызовDispose()
для очистки и запуска автоматического удаления компонентов.
Однако в этом случае я хочу, чтобы дочерние элементы удалялись автоматически.
Обходной путь, о котором я думал до сих пор, заключается в том, чтобы дочерняя область удаляла родительскую. Что-то вроде этого:
ILifetimeScope scope = ...;
var childScope = scope.BeginLifetimeScope();
childScope.Disposer.AddInstanceForDisposal(scope);
scope = childScope;
Поскольку я никогда не отслеживаю scope
только этот момент childScope
, мне нужен способ удаления родительского элемента.
Чтобы еще больше усложнить ситуацию, у каждого родителя может быть несколько дочерних областей. Поэтому я не могу этого сделать в этих случаях. Я хочу утилизировать родительский элемент только тогда, когда будет удален последний дочерний элемент. Чтобы сделать это, я думаю, в родительском мне пришлось бы зарегистрировать какую-то службу, предназначенную для подсчета ссылок при каждом BeginLifetimeScope()
вызове, и уменьшать это количество ссылок при удалении дочернего элемента.
Я не уверен, правильно ли я подхожу к этому, поэтому я хотел посмотреть, есть ли здесь лучшее решение. Я переношу свою кодовую базу из UnityContainer в Autofac. Ранее в коде был UnityDisposer
объект, который обходил родительское / дочернее дерево сверху вниз и удалял все, но я не получаю такую иерархию с Autofac.
Редактировать
Есть некоторые сомнения по поводу моего предполагаемого решения, поэтому я написал пример приложения, чтобы посмотреть, что произойдет:
class ThingA : IDisposable
{
public ThingA()
{
Console.WriteLine("Construct ThingA");
}
public void Dispose()
{
Console.WriteLine("Dispose ThingA");
}
}
class ThingB : IDisposable
{
public ThingB()
{
Console.WriteLine("Construct ThingB");
}
public void Dispose()
{
Console.WriteLine("Dispose ThingB");
}
}
public static class Program
{
public static void Main()
{
ContainerBuilder builder;
builder = new ContainerBuilder();
builder.RegisterType<ThingA>().InstancePerLifetimeScope();
ILifetimeScope container1 = builder.Build();
container1.Resolve<ThingA>();
var container2 = container1.BeginLifetimeScope(builder2 =>
{
builder2.RegisterType<ThingB>().InstancePerLifetimeScope();
});
container2.Disposer.AddInstanceForDisposal(container1);
container1 = container2;
container1.Resolve<ThingB>();
container1.Dispose();
}
}
Результат, который я получаю,:
Construct ThingA
Construct ThingB
Dispose ThingB
Dispose ThingA
Итак, из того, что я могу сказать, для линейных отношений родитель / потомок это, похоже, работает. Но это не решает проблему с несколькими дочерними элементами. И, чтобы было ясно, я тоже не особенно доволен решением для линейного случая.
Комментарии:
1. интересно, есть ли здесь конфликт — имеет ли смысл вообще удалять родительский элемент, когда дочерний элемент активен? это безопасно?
2. @DanielA. Белый Я тоже думал об этом, но поскольку родитель не распоряжается своими дочерними областями жизни, я предположил, что это сделало его безопасным. В любом случае, ваш вопрос точно такой же, как у меня, и является целью этого сообщения.
3. @void.pointer Почему вы хотите изменить переменную
scope
? Почему бы не позволить ему быть родительским и работать сchildScope
ним вместо этого? Но что более важно: Почему бы просто не вызватьscope.Disposer.AddInstanceForDisposal(childScope);
? Таким образом, дочерняя область будет удалена, когда родительская область будет удалена.4. Потому что мне нужно использовать дочернюю область для доступа к ее регистрациям. Родительская область служит только одной цели: предоставить базовое подмножество регистраций. Мою базу кода интересует только надмножество, которое доступно только от дочернего элемента. И поскольку я не заинтересован в отслеживании
parent
, это означает, что мне нужен дочерний элемент, чтобы убедиться, что он удален.5. @void.pointer Когда дочерний элемент должен быть удален?
Ответ №1:
С точки зрения Autofac вам действительно нужно будет начать отслеживать родительские и дочерние области, чтобы вы могли выбрать, когда их утилизировать. Это своего рода ситуация с Человеком-пауком: с большой властью приходит большая ответственность.
Иногда это так же просто, как обернуть набор различных единиц работы с using
помощью инструкции. В асинхронной ситуации, например, с ASP.NET , такие места, как промежуточное программное обеспечение, и HttpContext
вступают в игру.
Удаление родительского элемента из-под дочернего элемента — плохая идея. Помимо того факта, что после удаления родительского элемента вы не можете действительно разрешить проблемы с дочерним элементом (да, я понимаю, что родитель каким-то образом останется до конца последнего дочернего элемента, но факт остается фактом); мы определенно видели странные крайние случаи, в которые люди попадают, намеренно или непреднамеренно удаляя дочерний элемент.родительский элемент из-под дочернего элемента и, в зависимости от того, как он был подключен и в каком порядке все происходит, вы можете столкнуться с действительно сложными для устранения неполадок исключениями ObjectDisposedException
.
Если вы думаете о том, что вы пытаетесь сделать вне контекста Autofac, это своего рода плохой шаблон проектирования — пытаться заставить систему избавиться от самой себя, например, чтобы дочерняя область каким-то образом отвечала за родительскую. Например, если у вас было какое-то другое отношение родитель / потомок, где вы хотели это сделать, чаще всего вы бы увидели какой-то внешний оркестратор, который обрабатывает количество ссылок и удаление — вне логического отношения родитель / потомок.
Я бы рекомендовал вместо этого сделать что-то подобное — фактически отслеживать родительские и дочерние области с помощью некоторого класса orchestrator и заставить orchestrator следить за исчезновением всех дочерних областей.
Кроме того, это упростит другие ситуации, например: «Я создал родительский элемент, я создал одного дочернего элемента, он был удален и очищен родительский элемент, пока я пытался создать второго дочернего элемента». Многопоточность. Вы сможете учесть это в своем коде orchestrator.
Комментарии:
1. Я посмотрел на код Autofac. Средство удаления удаляется сразу после вызова события CurrentScopeEnding . Таким образом, семантически удаление родительского элемента в любом из этих местоположений не изменит проблему здесь. И оркестратору нужно будет полагаться на обратный вызов CurrentScopeEnding для удаления родительского элемента. Единственный способ сделать это безопасно — это «поставить в очередь» удаление родительского элемента после удаления дочернего элемента, но это сложно и неприятно.
LifetimeScope.Dispose()
Код не указывает на проблему с удалением родительского элемента во время удаления дочернего элемента.2. Сказав это, я пошел с этим в реализации orchestrator, она более чистая, а сам orchestrator настроен
InstancePerLifetimeScope()
таким образом, что каждый «родительский» контейнер владеет им, он отслеживает дочерние области и следит за их окончанием, а затем распоряжается своей областью владения. Как вы говорите, решение намного более гибкое, хотя оно представляет собой анти-шаблон…