c# - Cómo agregar miles de elementos a una colección enlazada sin bloquear la GUI
wpf performance (5)
Tengo una configuración donde potencialmente miles de elementos (piense 3000-5000) se agregarán a una ObservableCollection
que está enlazada a alguna interfaz visual. Actualmente, el proceso de agregarlos es bastante lento (aproximadamente 4 segundos / 1000 elementos) y, por supuesto, la GUI no responde durante ese tiempo. ¿Cuál es un buen método para manejar el traslado de tantos elementos a la vez en una colección sin preocuparse por el bloqueo del sistema? He consultado DispatcherTimer
pero no estoy seguro de si proporcionará todo lo que necesito.
Otra pregunta: ¿hay algo que pueda hacer para acelerar la creación de estos objetos para que no tarde tanto en agregarlos a la colección? Actualmente los uso así: Collection.Add(new Item(<params>))
¿Generar los elementos de antemano, probablemente en un hilo de fondo, disminuiría el tiempo necesario para agregarlos en una cantidad notable?
Edición: la virtualización no es posible. Los requisitos especifican un aspecto de WrapPanel
, por lo que la pantalla es en realidad un ListBox
que tiene un panel de elementos con plantilla.
Edit2: De acuerdo con el cronómetro, el cuello de botella en realidad está colocando artículos en mi ObservableCollection
. Intentaré cambiar ese tipo de colección y hacer mi propia notificación para ver si eso lo acelera sustancialmente.
Edit3: La respuesta está en un solo lugar: resolví este problema (con la ayuda de abajo) creando una clase que se hereda de ObservableCollection
. Esta clase hizo dos cosas: exponer un método para agregar colecciones a la vez, y agregó la capacidad de suprimir el evento CollectionChanged
. Con estos cambios, el tiempo que se tarda en agregar 3000 artículos es aproximadamente .4 segundos (97% de mejora). This enlace detalla la implementación de estos cambios.
El Binding
WPF admite la concurrencia por este motivo. Intente establecer Binding.IsAsync
en true. Adicionalmente.
- No use
ObservableCollection<T>
, es lento para esto porque cada vez que se agrega un elemento provoca eventos. Use algo más rápido comoList<T>
y levante la notificación de cambio de propiedad después de agregar todos sus artículos. - Pre-cree sus artículos en un hilo de fondo, luego insértelos en su colección.
- Verifique otras partes del código involucrado para ver si hay hinchazón y recorte.
Has dicho 1000, así que me limitaré a ese número, por ejemplo.
IIRC, la colección observable tiene un pequeño inconveniente: si agrega los elementos uno por uno, aumenta las notificaciones una vez por cada elemento. Eso significa que tienes 1000 notificaciones para 1000 de elementos y que el subproceso de la interfaz de usuario se ejecutará a una velocidad mortal solo para continuar con el redibujado de la pantalla.
¿Necesita volver a dibujar lo antes posible? Tal vez usted puede hacer un lote de las adiciones? Divida los 1000 artículos en unos pocos paquetes de 100 artículos, o un poco más de paquetes de 50 o 20 artículos. Luego, en lugar de colocar todos los elementos uno por uno, póngalos en paquetes. Pero tenga cuidado: tiene que usar algunos métodos como AddRange implementado por la propia colección, no por LINQ, o de lo contrario tendrá una inserción de uno por uno. Si encuentra dicho método, debería reducir el número de eventos de manera significativa, ya que la recopilación debería aumentar el evento de Cambio solo una vez por llamada a AddRange.
Si la colección observable no tiene AddRange, ya sea que use una colección diferente o escriba la suya, probablemente solo será suficiente un contenedor. El objetivo es NO aumentar el evento de Cambio en cada Add (), pero después de un recuento razonable de ellos, o, tal vez, simplemente omitir el aumento de Cambio cuando se agregan elementos y aumentar ¿Cambiado en algunos intervalos de tiempo regulares? Esto sería especialmente beneficioso si sus datos "fluyen" indefinidamente a una velocidad constante.
Por supuesto, en ese número de elementos que aparecen en la pantalla, puede que se le retenga en la propia imagen. Si sus Plantillas de artículos son complicadas, 1000 de los objetos multiplicados por 1000 de instancias de capas / propiedades visuales pueden simplemente matar la experiencia del usuario. ¿Ha simplificado los ItemTemplates al mínimo?
Lo último: considere usar la virtualización de StackPanels como ItemPanels en sus ItemsControl / ListBoxes. Puede reducir en gran medida la huella de memoria y el número de elementos dibujados en un solo punto de tiempo. Esto no necesariamente ayudará en el número o los eventos generados, pero puede ser de gran ayuda cuando tiene plantillas de elementos complejos.
Edit: está utilizando ObservableCollection, así que asumí que WPF / Silverlight ... actualizar la pregunta si esto no es correcto
Otra cosa que puedes probar: subclase ObservableCollection y hacer que admita la carga masiva (AddRange). Aquí hay un artículo: AddRange y ObservableCollection
Por solicitud, aquí es cómo resolví este problema. Comencé creando una clase que hereda de ObservableCollection
. Esta clase hizo dos cosas: exponer un método para agregar colecciones enteras a la vez, y agregó la capacidad de suprimir el evento CollectionChanged
. Con estos cambios, el tiempo que se tarda en agregar 3000 artículos es aproximadamente .4 segundos (97% de mejora). This enlace detalla la implementación de estos cambios.
para la segunda pregunta, si en su GUI está utilizando la tecnología WPF, puede aumentar el rendimiento con VirualizingStackPanel permitiéndole crear solo elementos visibles