Eliminación lenta de UserControl

Tengo dos controles en mi formulario: un cuadro de lista con una lista de trabajadores y un panel que actúa como contenedor para mostrar los detalles (tarjetas) sobre su trabajo. Cuando un usuario hace clic en el nombre del trabajador, muestro tarjetas en el panel. Una tarjeta es un control de usuario con una interfaz de usuario bastante simple (2 cuadros de grupo, 3 cuadros de texto y varias etiquetas) y una lógica simple (configuración del color de las etiquetas).

Las tarjetas se crean en tiempo de ejecución. Las tarjetas anteriores se eliminan del panel y se agregan las nuevas: la cantidad de tarjetas por trabajador es de 1 a 4. Aquí se pone interesante. Todo funciona bien hasta aprox. el quinto clic en los trabajadores. Parece que GC se activa y toma alrededor de dos segundos (0.3sx número de tarjetas previamente eliminadas) para que las tarjetas antiguas (anteriormente eliminadas) se eliminen y se muestren las nuevas. Si moverse entre trabajadores funciona muy bien antes, se vuelve dolorosamente lento en ese punto. Después de explorar un poco, localicé el problema para resolverlo. Dispose método de mi usedcontrol. Llamar base.Dispose() tarda unos 0.3 s.

Aquí está mi código:

private void ShowCards(List<Work> workItems) {
  var y = 5;
  panelControl1.SuspendLayout();
  panelControl1.Controls.Clear();

  foreach (var work in workItems) {
    var card = new Components.WorkDisplayControl(work);
    card.Top = y;
    card.Left = 10;

    y += card.Height + 5;

    panelControl1.Controls.Add(card);
  }

  panelControl1.ResumeLayout(true);
  Application.DoEvents();
}

Lo que he intentado hasta ahora:

  • ocultar tarjetas en lugar de desecharlas: funciona más rápido cuando se mueve entre trabajadores, pero la penalización se paga al cerrar el formulario
  • ocultar las tarjetas y tener un hilo separado que las deseche, sin cambios
  • pruebe agregando 10 tarjetas y desechándolas inmediatamente - lento
  • prueba agregando 10 tarjetas y desechándolas inmediatamente en el constructor - ¡RÁPIDO!
  • reemplazó los controles DevExpress con "normales" - sin cambios
  • desechar manualmente las tarjetas viejas en lugar de quitarlas al cambiar de trabajador; cada movimiento entre trabajadores se vuelve más lento: for (var ix = panelControl1.Controls.Count - 1; ix >= 0; --ix) { panelControl1.Controls[ix].Dispose();}
  • perfilándolo - así es como he encontrado el problema en Dispose. Puedo rastrearlo hasta Control.DestroyHandle
  • llamar Controls.Clear() in Dispose método de mi control: comportamiento súper extraño y excepciones
  • eliminé todos los controles de mi control de usuario: un poco más rápido, pero aún lento
  • ocultación panelControl1 al quitar y agregar tarjetas - sin cambios
  • apagó el GC de fondo, sin cambios
  • agregando tarjetas con AddRange

Dado que la misma funcionalidad funciona rápido cuando se llama desde el constructor, creo que la razón debe estar en algún lugar de los identificadores (de control).

Simplemente no puedo encontrar la razón de este extraño comportamiento. agradeceria alguna idea....

ACTUALIZACIÓN: mientras investigaba la conexión entre GC y Control.Dispose encontré esta excelente respuesta

preguntado el 22 de mayo de 12 a las 10:05

La eliminación no tiene nada que ver con el recolector de basura. Está utilizando Controls.Clear() de una manera muy peligrosa, no elimina los controles. Ejecute Taskmgr.exe, pestaña Procesos. Ver + Seleccionar columnas y marcar Objetos de USUARIO y Objetos GDI. Asegúrese de que estos valores sean estables para su aplicación y no suban sin límite. Póngase en contacto con devexpress para obtener más ayuda. -

@HansPassant Gracias por su comentario e ideas. El número de objetos USER y GDI no cambia. ¿Qué quieres decir con "camino peligroso"? Sólo quiero que se eliminen de mi contenedor. -

2 Respuestas

Después de [apoyo fallido de DevExpress], hice algunas pruebas y jugué con el código y finalmente encontré la solución.

El truco está en borrar los controles en el UserControl antes de desechar.

Uno puede modificar Dispose en el UC (esta solución funcionó en algunos casos, pero no en todos) u ocultar el UC en lugar de eliminarlo del panel y borrar su Controls

Solución 1:

protected override void Dispose(bool disposing) {
  if (disposing && (components != null)) {
    components.Dispose();
  }

  Controls.Clear(); // <--- Add this line

  base.Dispose(disposing); 
  }

Solución 2:

Agregue un nuevo método a la UC:

public void ClearControls() {
  Controls.Clear();
}

y en mi pregunta original reemplaza esta línea

panelControl1.Controls.Clear();

con este:

for (var ii = panelControl1.Controls.Count - 1; ii >= 0; --ii) { 
  var wdc = panelControl1.Controls[ii] as Components.WorkDisplayControl;
  wdc.Visible = false;
  wdc.ClearControls();
}

Funciona (al menos) 20 veces más rápido, lo cual es suficiente.

contestado el 27 de mayo de 12 a las 19:05

Me temo que esto no es una solución en absoluto... Esta es una forma peligrosa, porque tiene una fuga en las manijas de control (siempre debe desechar todos los controles no utilizados). Echa un vistazo a mi respuesta. Creo que puede ayudar. - DmitryG

La causa del problema no está relacionada con DevExpress ni con los controles estándar. Pero está relacionado con la creación y destrucción de controladores. Para mejorar la implementación de sus tarjetas, evite estas operaciones cuando sea posible. Le sugiero que use el almacenamiento en caché para sus tarjetas:

void ShowCards(List<Work> workItems) {
    cardsPanel.SuspendLayout();
    CacheCards(cardsPanel.Controls);
    int y = 5;
    foreach(var work in workItems) {
        var card = GetCardFromCache(work);
        card.Top = y;
        card.Left = 10;
        y += card.Height + 5;
        cardsPanel.Controls.Add(card);
    }
    cardsPanel.ResumeLayout(true);
}
//
Stack<WorkDisplayControl> cache;
void CacheCards(Control.ControlCollection controls) {
    if(cache == null)
        cache = new Stack<WorkDisplayControl>();
    foreach(WorkDisplayControl wdc in controls)
        cache.Push(wdc);
    controls.Clear();
}
WorkDisplayControl GetCardFromCache(Work data) {
    WorkDisplayControl result = (cache.Count > 0) ?
        cache.Pop() : new WorkDisplayControl();
    result.InitData(data);
    return result;
}

El próximo paso en la optimización de tarjetas es reducir el número total de manijas de control usadas. Dado que está utilizando los controles DevExpress, la mejor opción para usted es XtraLayoutControl. El uso de XtraLayoutControl le permite reducir significativamente el número total de tiradores de control. Solo crea 4 identificadores para el diseño que ha descrito (3 editores con etiquetas dentro de un par de cuadros de grupo) en lugar de 8 identificadores cuando se utilizan los controles estándar. XtraLayoutControl no crea identificadores para las etiquetas, grupos o pestañas de un editor. Por favor, también eche un vistazo a Vista de diseño XtraGrid - proporciona los beneficios del uso de la arquitectura de enlace de datos de la cuadrícula y la virtualización del diseño de las tarjetas sin ninguna codificación adicional.

contestado el 28 de mayo de 12 a las 07:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.