MEF сохраняет ссылки на NonShared IDisposable, не позволяя им собираться GC
Я столкнулся с некоторой проблемой в жизни части MEF, которая вызывает утечку памяти в моем приложении Prism.
Мое приложение экспортирует представления и режимы просмотра с установкой PartCreationPolicy
на CreationPolicy.NonShared
. Представления и viewmodels наследуют от ViewBase
и ViewModelBase
соответственно, что реализует IDisposable
.
Теперь, поскольку мои части реализуют IDisposable
, ссылка на них хранится в контейнере, что заставляет их не освобождаться сборщиком мусора. Согласно документации MEF по времени жизни, это по дизайну:
Контейнер не будет содержать ссылки на части, которые он создает, если не выполнено одно из следующих утверждений:
- Часть отмечена как
Shared
- Часть реализует
IDisposable
- Один или несколько импорта настроены так, чтобы разрешить перекомпоновку
Как я могу заставить MEF не ссылаться на эти части? Есть ли атрибут, который я могу использовать, чтобы MEF знал, что я не хочу, чтобы он сохранял ссылку на мою часть, даже если она реализует IDisposable
?
Обе стратегии, описанные в этой статье, не кажутся хорошими для меня решениями:
-
ReleaseExport
требуется объект Export
в качестве параметра, который я не знаю, как обеспечить. У меня есть экземпляр моего взгляда, но я не знаю, для чего был контракт, который использовался для создания представления. Было бы здорово, если бы была перегрузка для ReleaseExport
, которая могла бы получить любой объект, созданный контейнером.
- Использование дочернего контейнера не похоже на естественный вариант.
Любая помощь будет принята с благодарностью.
Ответы
Ответ 1
Если Prism не поддерживает какое-то время жизни для объектов вида, здесь нет решения, кроме как удалить IDisposable
из списка интерфейсов, отображаемых в представлении.
Существует три подхода MEF для решения этой проблемы, упомянутых другими респондентами:
-
ExportFactory<T>
- Контейнеры для детей
-
ReleaseExport()
Все они требуют некоторой работы со стороны кода, который запрашивает исходный экспорт - в этом случае код внутри Prism. Это имеет смысл, так как нежелательно, чтобы код, потреблявший объект, должен был знать, как и когда он был создан.
В MEF нет ReleaseExportedObject()
, поскольку многократное (например, свойство) экспорт может возвращать одно и то же значение; он может быть логически возможен, но добавленная сложность делает его маловероятным для решения MEF в обозримом будущем.
Надеюсь, что это поможет; Я повторил этот вопрос "призму", так как я уверен, что другие в сообществе Prism столкнутся с этим и смогут дать совет.
Ответ 2
Когда вы реализуете IDisposable
, вы говорите, что тип должен быть очищен детерминированным способом (вызывая IDisposable.Dispose
, а не случайным образом, когда сборщик мусора решает, что это время.
В вашем случае модели просмотра будут удаляться только при размещении контейнера, который, вероятно, не является тем, что вы хотите сделать. Чтобы обойти это, я вижу два возможных решения:
-
Не используйте IDisposable
в моделях просмотра. По-видимому, вам все равно, когда их убирают в любом случае, поэтому зачем их IDisposable
?
-
Вместо того, чтобы позволить контейнеру создавать каждую модель представления без общего доступа, вы можете использовать класс общей модели представления factory. Затем вы можете применить этот класс к владельцам моделей просмотра, чтобы позволить владельцам явно создавать модели представлений. Предположительно, эти владельцы также знали бы, когда нужно распоряжаться моделями просмотра.
В принципе, если что-то одноразовое, что также должно быть разумной точкой в вашем коде, где вам нужно распоряжаться тем, что является одноразовым.
Ответ 3
Вы должны создать эти экземпляры через импортированную ExportFactory <T>. Затем у вас будет необходимый элемент управления для их удаления с помощью ExportLifetimeContext<T>.Dispose()
.
Однако, это доступно только из коробки в следующей версии .NET(4.5) или в последних выпусках MEF предварительного просмотра на codeplex. (В более старых версиях MEF та же функциональность была реализована в качестве образца и называлась PartCreator, как описано в этом сообщении в блоге.)
Ответ 4
Все остальные ответы дают хорошие способы обойти эту проблему, но в итоге я в конечном итоге использовал свой собственный интерфейс ICleanup
вместо IDisposable
. Это, конечно, не подходит для всех.