Утилизировать компоненты контейнером MEF?

Я использую MEF для сопоставления интерфейса с классом реализации как способом DI. Например, я использую атрибут Import для интерфейса и Export для класса реализации. Насколько я понимаю, фреймворк MEF создаст экземпляры класса реализации и будет хранить их в контейнере MEF для использования или автоматической инъекции.

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

Вот несколько примеров кода для сопоставления импорта и экспорта:

[Import]
private IMyInterface IComponent1 { get; set; }
....

[Export]
private IMyInterface Component {
  get {
     var instance = new MyImplemetation();
     ....
     return instance;
 }
}
....

Аналогичным образом существует множество других определений импорта и экспорта для других отображений. Я строю сопоставления таким образом, чтобы MEF знал об отношениях и способах создания сопоставленных экземпляров. Вот несколько кодов в моем приложении для загрузки сопоставлений с помощью AssemblyCatalog:

var catalog = new AggregateCatalog();
catalog.Add (new AssemblyCatalog(Assembly.GetExecutingAssembly());
var batch = new CompositionBatch();
batch.AddPart(catalog);
// MEF container has all the mappings
var container = new CompositionContainer(catalog);
....
// Get instance from container
var instance = container.GetExportedValue<IMyInterface>();
// my instance CTOR has a contructor with several other 
// implementation instances injected by interface
// instance starts to do its job and coordinates others ...
instance.Start();
....
// Finally the job is done.
// Dispose the container explicitly there.
container.Dispose();
// But my components are never disposed
// this results some connections not being closed
// file streams not being closed...

Здесь у экземпляра есть много других компонентов, введенных MEF через CTOR. Эти компоненты также содержат другие компоненты, которые вводятся MEF. Проблема в том, что действительно сложно принять решение, когда удалять компоненты, поскольку некоторые экземпляры являются общими. Если я вызову Dispose для одного, это приведет к тому, что другие не смогут его использовать. Как вы можете видеть на этом рисунке, экземпляры создаются MEF и внедряются в классы моего приложения. Каждый компонент не должен знать других, и он должен использовать внедренные компоненты для выполнения своей работы.

Я не уверен, где и как я должен указать MEF для вызова Dispose для компонентов, когда приложение завершается или контейнер удаляется? Стоит ли вызывать Dispose для компонентов? Я не думаю, что это правильно, поскольку MEF создает их и вводит их клиентам по мере необходимости. Клиенты не должны вызывать Dispose по окончании работы.


person David.Chu.ca    schedule 24.02.2010    source источник


Ответы (2)


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

public class ComponentExporter : IDisposable
{
    private IMyInterface _component;

    [Export]
    public IMyInterface Component
    {
        get
        {
            if (_component != null)
            {
                _component = new MyImplementation();

                // ...
            }
            return _component;
        }
    }

    public void Dispose()
    {
        if (_component != null)
        {
            _component.Dispose();
        }
    }
}

ComponentExporter - это класс, фактически созданный MEF, и если он реализует IDisposable, MEF избавится от него вместе с контейнером. В этом примере ComponentExporter удаляет созданный компонент при его удалении, что, вероятно, именно то, что вам нужно.

Конечно, было бы проще, если бы вы просто поместили экспорт непосредственно в класс MyImplementation. Я предполагаю, что у вас есть причины не делать этого, но это будет выглядеть так:

[Export(typeof(IMyInterface))]
public class MyImplementation : IMyInterface, IDisposable
{
    // ...
}

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

person Daniel Plaisted    schedule 24.02.2010
comment
Я думаю, что Даниэль хорошо это объяснил. Я создал экземпляр в моем получателе свойств Export. Имеет смысл держать экземпляр и убирать оттуда. Я предпочитаю ставить экспорт в геттер вместо класса. Я протестирую его и сообщу, решит ли это проблему. - person David.Chu.ca; 25.02.2010

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

Мне нравится предложение Дэниела ввести класс для моей части экспорта. Поскольку все мои сопоставления DI определены в виде свойств (получателей и сеттеров), я создал такой базовый класс:

public class ComponentExporterBase: IDisposable {
  private List<IDisposable> _list;

  public ComponentExporterBase()  {
    _list = new List<IDisposable>();
  }

  protect void Add(IDisposable obj) {
    _list.Add(obj);
  }

  protected virtual void Dispose(bool disposing) {
    if (disposing) {
      foreach(var obj in _list) {
        obj.Dispose();
      }
      _list.Clear();
    }
  }  

  public void Dispose()  {
    Dispose(true);
  }
}

С этим базовым классом мои классы сопоставления смогут позволить MEF выполнять работу по удалению. Например, вот один пример:

internal class MyDIMappingClass : ComponentExporterBase {
  [Import]
  private IDataReader _dataReader { get; set; }

  [Export]
  private IController {
      get {
         var reader = _dataReader;
         var instance = new MyMainController(reader);
         base.Add(instance);
         return instance;
  }
  ...
}

Все мои классы отображения определены аналогичным образом, и они намного чище. Основной принцип заключается в том, что экземпляры или ресурсы, созданные внутри класса, должны располагаться внутри класса, а не внедренные экземпляры. Таким образом, мне больше не нужно очищать какие-либо экземпляры, внедренные MEF, как в этом примере:

public class MyMainController : IController {
   private IDataReader _dataReader;

   // dataReader is injected through CTOR
   public MyMainControler(IDataReader dataReader) {
     _dataReader = dataReader; 
     ...
   }
   ...
   public void Dispose() {
     // dispose only resources created in this class
     // _dataReader is not disposed here or within the class!
     ...}
}

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

person David.Chu.ca    schedule 25.02.2010