2010-03-24 12 views
15

Tengo un control que tiene sus datos vinculados a un estándar ObservableCollection, y tengo una tarea en segundo plano que llama a un servicio para obtener más datos.Encuadernación de actualización WPF en un hilo de fondo

Quiero, entonces, actualizar mis datos de respaldo detrás de mi control, mientras se muestra un cuadro de diálogo "espere por favor", pero cuando agrego los nuevos elementos a la colección, el subproceso de UI se bloquea mientras se vincula y actualiza mis controles.

¿Puedo evitar esto para que mis animaciones y cosas continúen ejecutándose en mi diálogo "espere por favor"?

O al menos darle la "apariencia" al usuario de que no está bloqueado?

Respuesta

17

si he entendido bien, ya utiliza un BackgroundWorker para recuperar los datos, y que simplemente asignar estos datos a la ObservableCollection se bloquea la interfaz de usuario .

Una forma de evitar el bloqueo de la interfaz de usuario es asignar los datos al ObservableCollection en trozos más pequeños al poner en cola múltiples métodos de despachador. Entre cada llamada a un método, se pueden manejar eventos de IU.

Lo siguiente agregaría un elemento a la vez, eso es un poco extremo, pero ilustra el concepto.

void UpdateItems() 
{ 
    //retrievedItems is the data you received from the service 
    foreach(object item in retrievedItems) 
     Dispatcher.BeginInvoke(DispatcherPriority.Background, new ParameterizedThreadStart(AddItem), item);  
} 

void AddItem(object item) 
{ 
    observableCollection.Add(item); 
} 
+0

Entonces, al agregarlos a la colección de elementos en un hilo de fondo ¿se actualizarán los controles en el hilo de la interfaz de usuario? ¿Cómo funciona? – Mark

+3

No, los agrega en el subproceso de despachador, pero con una prioridad más baja y en chucnks más pequeños para que la UI siga siendo receptiva – Bubblewrap

+0

Ya veo, gracias voy a intentarlo – Mark

0

use BackgroundWorker para realizar esta tarea. actualizar el obsrvablecollection en el método DoWork

+0

¿Cómo es que la actualización del hilo de interfaz de usuario? Tal vez no estoy entendiendo esto, pero ¿no están todos mis controles y la representación detrás de ellos en el hilo de la interfaz de usuario, por lo que la actualización de la colección en un hilo de fondo solo hará que el hilo de la interfaz de usuario haga la actualización normal? – Mark

-1

Utilice esta:

 

Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new Action(UpdateData), value); 

private void UpdateData(int value) 
{ 
    BindingSourceProperty = value; 
} 

 
+1

¿qué hace? ¿Por qué es bueno? ¿Cómo me ayudará eso? – Mark

+0

Digamos que tiene un formulario WPF UI con control ProgressBar como donde PercentValue es propiedad definida en su DataContext adjunto al formulario. Suponiendo un escenario en el que está obteniendo datos de devolución de llamada en otro hilo que indica el progreso de la operación y desea mostrarlo en la interfaz de usuario. Si establece ese valor directamente en su propiedad de fuente de dependencia 'PercentValue', se actualizará ui solo cuando la operación de hilo de fondo haya finalizado. Entonces, para obtener un progreso en tiempo real del valor porcentual, establezca este valor como en el anterior – RockWorld

10

ObservableCollection elevará eventos CollectionChanged que obligarán a la interfaz de usuario para volver a vincular los datos, medir, organizar y volver a dibujar. Esto puede llevar mucho tiempo si tiene muchas actualizaciones por venir.

Es posible hacer que el usuario piense que la IU está activa al dividir el trabajo en paquetes pequeños. Use Dispatcher desde el hilo de la interfaz de usuario (cualquier control tiene referencia) para programar acciones de actualización de la colección con 10-100 elementos (determine el número por experimento, estos solo para respaldar la idea).

Su fondo del código fuerza se ve así:

void WorkInBackground() 
{ 
    var results = new List<object>(); 

    //get results... 

    // feed UI in packages no more than 100 items 
    while (results.Count > 0) 
    { 
     Application.Current.MainWindow.Dispatcher.BeginInvoke(
      new Action<List<object>>(FeedUI), 
      DispatcherPriority.Background, 
      results.GetRange(0, Math.Min(results.Count, 100))); 
     results.RemoveRange(0, Math.Min(results.Count, 100)); 
    } 
} 
void FeedUI(List<object> items) 
{ 
    // items.Count must be small enough to keep UI looks alive 
    foreach (var item in items) 
    { 
     MyCollection.Add(item); 
    } 
} 
0

Tengo un archivo DLL que se ejecuta un subproceso de trabajo y envía eventos de nuevo a la aplicación - funcionado perfectamente en formas de las ventanas, cambiado a WPF y todo lo dejado de funcionar. He estado golpeando mi cabeza contra una pared de ladrillo durante 4 horas tratando de hacer que esto funcione. Pero la solución con la que terminé, gracias a UI Thread Safe marshalling de Microsoft EnableCollectionSynchronization, ofrece una implementación realmente limpia para resolver esto.

Esta colección amplía ObservableCollection e implementa EnableCollectionSynchronization haciendo que estos objetos se puedan utilizar entre WPF y también trabajadores en segundo plano.

Edición: Microsoft's docs decir lo siguiente, así que voy a asumir que el intercambio de contexto del objeto, no importa.

El parámetro de contexto es un objeto arbitrario que puede usar para obtener información conocida cuando habilita la sincronización de recopilación. El contexto puede ser nulo.

ThreadSafeCollection.cs

using System.Collections.ObjectModel; 
using System.Windows.Data; 

namespace NSYourApplication 
{ 
    /// <summary> 
    /// This ObservableCollection is thread safe 
    /// You can update it from any thread and the changes will be safely 
    /// marshalled to the UI Thread WPF bindings 
    /// Thanks Microsoft! 
    /// </summary> 
    /// <typeparam name="T">Whatever type of collection you want!</typeparam> 
    public class ThreadSafeCollection<T> : ObservableCollection<T> 
    { 
     private static object __threadsafelock = new object(); 

     public ThreadSafeCollection() 
     { 
      BindingOperations.EnableCollectionSynchronization(this, __threadsafelock); 
     } 
    } 
} 

Ejemplo WindowViewModel WindowViewModel.cs

namespace NSYourApplication 
{ 
    /// <summary> 
    /// Example View 
    /// BaseModelView implements "PropertyChanged" to update WPF automagically 
    /// </summary> 
    class TestViewModel : BaseModelView 
    { 
     public ThreadSafeCollection<string> StringCollection { get; set; } 

     /// <summary> 
     /// background thread implemented elsewhere... 
     /// but it calls this method eventually ;) 
     /// Depending on the complexity you might want to implement 
     /// [MethodImpl(MethodImplOptions.Synchronized)] 
     /// to Synchronize multiple threads to prevent chase-conditions,deadlocks etc 
     /// </summary> 
     public void NonUIThreadMethod() 
     { 
      // No dispatchers or invokes required here! 
      StringCollection.Add("Some Text from a background worker"); 
     } 

     /// <summary> 
     /// Somewhere in the UIThread code it'll call this method 
     /// </summary> 
     public void UIThreadMethod() 
     { 
      StringCollection.Add("This text come from UI Thread"); 
     } 

     /// <summary> 
     /// Constructor, creates a thread-safe collection 
     /// </summary> 
     public TestViewModel() 
     { 
      StringCollection = new ThreadSafeCollection<string>(); 
     } 
    } 
} 

de uso en un cuadro de lista en un xaml ventana/de control MainWindow.xaml

<ListBox x:Name="wpfStringCollection" ItemsSource="{Binding StringCollection,Mode=OneWay}"> 

    </ListBox> 
+0

Hmm pensando en eso; este único __threadsafelock estático va a ser compartido por todas las colecciones; esto podría causar problemas;) Tendré que volver a esto ... =] –

Cuestiones relacionadas