Автоматическое удаление дочерних областей жизни в Autofac

#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() таким образом, что каждый «родительский» контейнер владеет им, он отслеживает дочерние области и следит за их окончанием, а затем распоряжается своей областью владения. Как вы говорите, решение намного более гибкое, хотя оно представляет собой анти-шаблон…