Tengo un ObservableCollection<A> a_collection;La colección contiene 'n' elementos. Cada elemento A se ve así:
public class A : INotifyPropertyChanged
{
public ObservableCollection<B> b_subcollection;
Thread m_worker;
}
Básicamente, todo está conectado a una vista de lista de WPF + un control de vista de detalles que muestra el b_subcollectionelemento seleccionado en una vista de lista separada (enlaces bidireccionales, actualizaciones de propiedad modificada, etc.).
El problema apareció para mí cuando comencé a implementar subprocesos. La idea era que todo el mundo a_collectionusara su hilo de trabajo para "hacer el trabajo" y luego actualizar sus respectivos b_subcollectionsy hacer que la interfaz gráfica de usuario mostrara los resultados en tiempo real.
Cuando lo probé, obtuve una excepción que decía que solo el hilo Dispatcher puede modificar un ObservableCollection y el trabajo se detuvo.
¿Alguien puede explicar el problema y cómo solucionarlo?

Respuestas:
Técnicamente, el problema no es que esté actualizando ObservableCollection desde un hilo en segundo plano. El problema es que cuando lo haces, la colección genera su evento CollectionChanged en el mismo hilo que causó el cambio, lo que significa que los controles se actualizan desde un hilo en segundo plano.
Para completar una colección desde un hilo en segundo plano mientras los controles están vinculados a ella, probablemente tenga que crear su propio tipo de colección desde cero para solucionar este problema. Sin embargo, hay una opción más simple que puede funcionar para usted.
Publique las llamadas Add en el hilo de la interfaz de usuario.
public static void AddOnUI<T>(this ICollection<T> collection, T item) { Action<T> addMethod = collection.Add; Application.Current.Dispatcher.BeginInvoke( addMethod, item ); } ... b_subcollection.AddOnUI(new B());Este método regresará inmediatamente (antes de que el elemento se agregue realmente a la colección), luego en el hilo de la interfaz de usuario, el elemento se agregará a la colección y todos deberían estar felices.
La realidad, sin embargo, es que esta solución probablemente se atascará bajo una carga pesada debido a toda la actividad de los hilos cruzados. Una solución más eficiente agruparía un montón de elementos y los publicaría en el hilo de la interfaz de usuario periódicamente para que no esté llamando a los hilos para cada elemento.
La clase BackgroundWorker implementa un patrón que le permite informar el progreso a través de su método ReportProgress durante una operación en segundo plano. El progreso se informa en el hilo de la interfaz de usuario a través del evento ProgressChanged. Esta puede ser otra opción para ti.
fuente
Nueva opción para .NET 4.5
A partir de .NET 4.5 hay un mecanismo integrado para sincronizar automáticamente el acceso a la colección y enviar
CollectionChangedeventos al subproceso de la interfaz de usuario. Para habilitar esta función, debe llamar desde su hilo de interfaz de usuario .BindingOperations.EnableCollectionSynchronizationEnableCollectionSynchronizationhace dos cosas:CollectionChangedeventos en ese subproceso.Muy importante, esto no se encarga de todo : para garantizar el acceso seguro para subprocesos a una colección intrínsecamente no segura para subprocesos, debe cooperar con el marco adquiriendo el mismo bloqueo de sus subprocesos en segundo plano cuando la colección está a punto de ser modificada.
Por tanto, los pasos necesarios para un correcto funcionamiento son:
1. Decida qué tipo de bloqueo utilizará
Esto determinará qué sobrecarga se
EnableCollectionSynchronizationdebe utilizar. La mayoría de las veces, unalockdeclaración simple será suficiente, por lo que esta sobrecarga es la opción estándar, pero si está utilizando algún mecanismo de sincronización elegante, también hay soporte para bloqueos personalizados .2. Cree la colección y habilite la sincronización
Según el mecanismo de bloqueo elegido, llame a la sobrecarga adecuada en el subproceso de la interfaz de usuario . Si utiliza una
lockdeclaración estándar , debe proporcionar el objeto de bloqueo como argumento. Si utiliza la sincronización personalizada, debe proporcionar unCollectionSynchronizationCallbackdelegado y un objeto de contexto (que puede sernull). Cuando se invoca, este delegado debe adquirir su bloqueo personalizado, invocar el que se le haActionpasado y liberar el bloqueo antes de regresar.3. Coopere bloqueando la colección antes de modificarla.
También debe bloquear la colección utilizando el mismo mecanismo cuando esté a punto de modificarla usted mismo; Haga esto con
lock()el mismo objeto de bloqueo que se pasóEnableCollectionSynchronizationen el escenario simple, o con el mismo mecanismo de sincronización personalizado en el escenario personalizado.fuente
BeginInvokepara ejecutar un método que realice todos los cambios apropiados en el hilo de la interfaz de usuario [como máximo unoBeginInvokeestaría pendiente en un momento dado.Con .NET 4.0 puede utilizar estos conceptos básicos:
.AddApplication.Current.Dispatcher.BeginInvoke(new Action(() => this.MyObservableCollection.Add(myItem)));.RemoveApplication.Current.Dispatcher.BeginInvoke(new Func<bool>(() => this.MyObservableCollection.Remove(myItem)));fuente
Código de sincronización de colección para la posteridad. Utiliza un mecanismo de bloqueo simple para permitir la sincronización de la colección. Tenga en cuenta que deberá habilitar la sincronización de colecciones en el hilo de la interfaz de usuario.
public class MainVm { private ObservableCollection<MiniVm> _collectionOfObjects; private readonly object _collectionOfObjectsSync = new object(); public MainVm() { _collectionOfObjects = new ObservableCollection<MiniVm>(); // Collection Sync should be enabled from the UI thread. Rest of the collection access can be done on any thread Application.Current.Dispatcher.BeginInvoke(new Action(() => { BindingOperations.EnableCollectionSynchronization(_collectionOfObjects, _collectionOfObjectsSync); })); } /// <summary> /// A different thread can access the collection through this method /// </summary> /// <param name="newMiniVm">The new mini vm to add to observable collection</param> private void AddMiniVm(MiniVm newMiniVm) { lock (_collectionOfObjectsSync) { _collectionOfObjects.Insert(0, newMiniVm); } } }fuente