2009-12-07 25 views
13

Tengo una aplicación WPF basada en PRISM que utiliza el patrón MVVM.Cuál es la mejor manera de evitar pérdidas de memoria en la aplicación WPF PRISM/MVVM

He notado que ocasionalmente mis modelos de vista, vistas y todo lo relacionado con ellos se mantendrán durante mucho tiempo después de su vida prevista.

Una fuga implicaba suscribirse a CollectionChanged en una colección perteneciente a un servicio inyectado, otra implicaba no llamar al método Stop en un DispatcherTimer, y otra requería que una colección se liberara de sus artículos.

Creo que usar CompositePresentationEvent es preferible a suscribirse a CollectionChanged, pero en los otros escenarios me inclino por implementar IDisposable y hacer que las vistas invoquen el método Dispose en los modelos de visualización.

Pero luego algo necesita decirle a la vista cuándo llamar a Dispose en el modelo de vista, que se vuelve incluso menos atractivo cuando aumenta la complejidad de las vistas, y comienzan a incluir vistas secundarias.

¿Cuál cree que es el mejor enfoque para manejar modelos de vista, para asegurar que no pierdan memoria?

Gracias de antemano

Ian

Respuesta

14

te puedo decir que he experimentado el 100% del dolor que han experimentado. Somos hermanos de fuga de memoria, creo.

Desafortunadamente, lo único que he descubierto aquí es algo muy similar a lo que estás pensando.

Lo que hemos hecho es crear una propiedad adjunta una vista que se puede aplicar a sí mismo para unirse a un controlador del modelo de vista:

<UserControl ... 
      common:LifecycleManagement.CloseHandler="{Binding CloseAction}"> 
... 
</UserControl> 

A continuación, nuestro modelo de vista simplemente tiene un método en el que el tipo de acción:

public MyVM : ViewModel 
{ 
    public Action CloseAction 
    { 
      get { return CloseActionInternal; } 
    } 

    private void CloseActionInternal() 
    { 
      //TODO: stop timers, cleanup, etc; 
    } 
} 

Cuando mis cercanos incendios método (que tienen un par de maneras de hacer esto ... es una interfaz de usuario TabControl con "X" en la cabecera de la ficha, ese tipo de cosas), simplemente comprobar para ver si este punto de vista se ha registrado con AttachedProperty. Si es así, invoco el método al que se hace referencia allí.

Es una forma bastante indirecta de simplemente verificar si DataContext of a View es un IDisposable, pero se sintió mejor en ese momento. El problema con la comprobación de DataContext es que podría tener modelos de vista secundaria que también necesitan este control. Debería asegurarse de que su modelo de vista reenvíe esta llamada a disposición o verifique todas las vistas en el gráfico y vea si sus contextos de datos son IDisposables (ugh).

Tengo la sensación de que aquí falta algo. Hay algunos otros marcos que intentan mitigar este escenario de otras maneras. Puede echar un vistazo al Caliburn. Tiene un sistema para manejar esto, en el que ViewModel conoce todos los modelos de subvistas y esto le permite encadenar automáticamente las cosas. En particular, hay una interfaz llamada ISupportCustomShutdown (creo que así se llama) que ayuda a mitigar este problema.

Lo mejor que he hecho, sin embargo, es asegurarme y usar buenas herramientas de pérdida de memoria como Redgate Memory Profiler que te ayudan a visualizar el gráfico de objetos y encontrar el objeto raíz. Si fue capaz de identificar ese problema DispatchTimer, me imagino que ya está haciendo esto.

Editar: Olvidé una cosa importante. Existe una posible pérdida de memoria causada por uno de los controladores de eventos en DelegateCommand. Aquí hay un hilo sobre esto en Codeplex que lo explica. http://compositewpf.codeplex.com/WorkItem/View.aspx?WorkItemId=4065

La última versión del Prism (v2.1) tiene esto fijo. (http://www.microsoft.com/downloads/details.aspx?FamilyID=387c7a59-b217-4318-ad1b-cbc2ea453f40&displaylang=en).

+0

LOL, este es un agujero profundo que he estado cavando aquí;) Tenía curiosidad sobre cómo se conecta su comportamiento adjunto en el UserControl, he estado buscando alguna manera de detectar cuándo se va a terminar la vista. Descargar parece ser la opción obvia, pero no es de mucha ayuda, ya que se activará cada vez que la vista se vuelva inactiva –

+0

Ah, es personalizado.Siempre que una vista deba cerrarse llamará a un método en un conjunto de comandos de aplicación que tenemos. Cerrar vista (vista de objeto). Verifico con la tienda detrás de la propiedad adjunta para ver si esa vista o cualquiera de sus vistas secundarias están registradas para tener manejadores cercanos. Si es así, llamo al método que registraron. La única magia aquí es el uso de las clases LogicalTreeHelper y VisualTreeHelper para localizar vistas secundarias. http://msdn.microsoft.com/en-us/library/system.windows.media.visualtreehelper.aspx –

+0

Intentamos descargar también, por cierto. En algunos casos, eso estaba bien ... detendríamos los temporizadores, que básicamente eran solo datos refrescantes, y no había ninguna razón para actualizarlo si la vista no era visible (como si estuviera en pestañas o algo así), pero a veces solo necesito un temporizador que nunca se detenga a menos que la vista se mate y este cierre explícito fue lo mejor que se nos ocurrió. –

2

Mis hallazgos hasta ahora ...

Además de PRISM, Unidad, MVVM WPF y también estamos utilizando Entity Framework y la red de datos Xceed. La creación de perfiles de memoria se realizó con dotTrace.

Terminé implementando IDisposable en una clase base para mis modelos de vista con el método Dispose (bool) que se declara virtual, lo que permite a las subclases la posibilidad de limpiar también. Como cada modelo de vista en nuestra aplicación obtiene un contenedor hijo de Unity, lo desechamos también, en nuestro caso esto asegura que el ObjectContext de EF se salió del alcance. Esta fue nuestra principal fuente de pérdidas de memoria.

El modelo de vista está dispuesto dentro de un método explícito CloseView (UserControl) en una clase de controlador base. Busca un IDisposable en el DataContext de la vista y llama a Dispose on it.

La cuadrícula de datos Xceed parece estar causando una buena parte de las fugas, especialmente en vistas de larga ejecución. Cualquier vista que actualice el ItemSource de la cuadrícula de datos asignando una nueva colección debería llamar a Clear() en la colección existente antes de asignar la nueva.

Tenga cuidado con Entity Framework y evite cualquier contexto de objetos de larga ejecución. Es muy implacable cuando se trata de colecciones grandes, aunque hayas eliminado la colección si el seguimiento está activado, se mantendrá una referencia a cada elemento de la colección, aunque ya no estés pendiente de ellos.

Si no necesita actualizar la entidad, recupérela con MergeOption.NoTracking, especialmente en vistas de larga duración que enlazan a colecciones.

Evite las vistas de larga duración, no las sostenga dentro de una región cuando no son visibles, esto le causará dolor especialmente si actualizan sus datos a intervalos regulares cuando son visibles.

Al usar CellContentTemplates en la Columna Xceed, no use recursos dinámicos ya que el recurso mantendrá una referencia a la celda, que a su vez mantendrá viva la vista completa.

Al usar CellEditor en la Columna Xceed y el recurso se almacena en un diccionario de recursos externo, agregue x: Shared = "False" al recurso que contiene el CellEditor, una vez más el recurso tendrá una referencia a la celda, usando x : Shared = "False" le asegura que obtendrá una copia nueva cada vez, y la anterior se eliminará correctamente.

Tenga cuidado al vincular DelegateCommand a elementos dentro de la cuadrícula de datos Exceed, si tiene un caso como un botón Eliminar en la fila que se une a un comando, asegúrese de borrar la colección que contiene ItemsSource antes de cerrar la vista . Si está actualizando la colección, también necesita reiniciar el comando, así como el comando mantendrá una referencia a cada fila.

+0

Eso trae a colación un buen punto que debería mencionar. La última versión lanzada de Prism tiene una potencial pérdida de memoria en DelegateCommand. Debería haber estado utilizando referencias débiles para uno de los controladores de eventos que tenía, pero no fue así. Terminé bifurcando el código y solucionándolo yo mismo, pero desde entonces se han corregido en la fuente que se comprueba en el repositorio en codeplex. Despliegue la última fuente y compile y también debería evitar esta. –

Cuestiones relacionadas