c# wpf xaml silverlight mvvm

c# - Este tipo de CollectionView no admite cambios en su SourceCollection desde un hilo diferente del hilo Dispatcher



wpf xaml (6)

Dado que su ObservableCollection se crea en el subproceso de interfaz de usuario, solo puede modificarlo desde el subproceso de interfaz de usuario y no desde otros subprocesos. Esto se denomina afinidad de subprocesos .

Si alguna vez necesita actualizar los objetos creados en el subproceso de la interfaz de usuario desde un subproceso diferente, simplemente put the delegate on UI Dispatcher y eso funcionará para que lo put the delegate on UI Dispatcher subproceso de la interfaz de usuario. Esto funcionará -

public void Load() { matchList = new List<GetMatchDetailsDC>(); matchList = proxy.GetMatch().ToList(); foreach (EfesBet.DataContract.GetMatchDetailsDC match in matchList) { App.Current.Dispatcher.Invoke((Action)delegate // <--- HERE { _matchObsCollection.Add(match); }); } }

Tengo un DataGrid que está poblando datos de ViewModel por método asincrónico. My DataGrid es:

<DataGrid ItemsSource="{Binding MatchObsCollection}" x:Name="dataGridParent" Style="{StaticResource EfesDataGridStyle}" HorizontalGridLinesBrush="#DADADA" VerticalGridLinesBrush="#DADADA" Cursor="Hand" AutoGenerateColumns="False" RowDetailsVisibilityMode="Visible" >

Estoy usando http://www.amazedsaint.com/2010/10/asynchronous-delegate-command-for-your.html para implementar una manera asíncrona en mi viewmodel.

Aquí está mi código de viewmodel:

public class MainWindowViewModel:WorkspaceViewModel,INotifyCollectionChanged { MatchBLL matchBLL = new MatchBLL(); EfesBetServiceReference.EfesBetClient proxy = new EfesBetClient(); public ICommand DoSomethingCommand { get; set; } public MainWindowViewModel() { DoSomethingCommand = new AsyncDelegateCommand( () => Load(), null, null, (ex) => Debug.WriteLine(ex.Message)); _matchObsCollection = new ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC>(); } List<EfesBet.DataContract.GetMatchDetailsDC> matchList; ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC> _matchObsCollection; public ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC> MatchObsCollection { get { return _matchObsCollection; } set { _matchObsCollection = value; OnPropertyChanged("MatchObsCollection"); } } // public void Load() { matchList = new List<GetMatchDetailsDC>(); matchList = proxy.GetMatch().ToList(); foreach (EfesBet.DataContract.GetMatchDetailsDC match in matchList) { _matchObsCollection.Add(match); } }

Como puede ver en mi método Load () en mi ViewModel, primero obtengo matchList (que es una lista de una clase DataContract) de mi servicio. Luego, mediante el bucle foreach, inserto mis elementos MatchList en mi _matchObsCollection (que es una ObservableCollection de DataContract Class)). Ahora aquí obtengo el error anterior (como se muestra en el Título) "Este tipo de CollectionView no admite cambios en su SourceCollection de un hilo diferente del hilo Dispatcher"

¿Puede alguien sugerirme alguna solución? Además, si es posible, me gustaría saber cómo vincular mi DataGrid en View y también actualizarlo de forma asíncrona si existe alguna forma mejor.


En mi caso ( ObservableCollection con tareas asíncronas y no tengo acceso a la instancia de la App ) utilizo TaskScheduler.FromCurrentSynchronizationContext() para limpiar la colección en caso de error:

// some main task Task loadFileTask = Task.Factory.StartNew(...); Task cleanupTask = loadFileTask.ContinueWith( (antecedent) => { CleanupFileList(); }, /* do not cancel this task */ CancellationToken.None, /* run only if faulted main task */ TaskContinuationOptions.OnlyOnFaulted, /* use main SynchronizationContext */ TaskScheduler.FromCurrentSynchronizationContext());



Puedes hacerlo:

App.Current.Dispatcher.Invoke((System.Action)delegate { _matchObsCollection.Add(match) });

Para .NET 4.5+: puede seguir la respuesta de Daniel. En su ejemplo, usted le da la responsabilidad al editor de que necesita llamar o invocar el hilo correcto:

var uiContext = SynchronizationContext.Current; uiContext.Send(x => _matchObsCollection.Add(match), null);

O puede poner la responsabilidad en su servicio / modelo de vista / lo que sea y simplemente habilitar CollectionSynchronization. De esta forma, si realiza una llamada, no tiene que preocuparse por el hilo en el que se encuentra y en cuál realiza la llamada. La responsabilidad ya no es para el editor. (Esto puede ocasionarle un poco de sobrecarga en el rendimiento pero al hacerlo en un servicio central, puede ahorrarle muchas excepciones y le brinda un mantenimiento de la aplicación más sencillo).

private static object _lock = new object(); public MainWindowViewModel() { // ... _matchObsCollection = new ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC>(); BindingOperations.EnableCollectionSynchronization(_matchObsCollection , _lock); }

Más información: https://msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.enablecollectionsynchronization(v=vs.110).aspx

En Visual Studio 2015 (Pro), vaya a Depurar -> Windows -> Subprocesos para depurar fácilmente y ver en qué subprocesos se encuentra.


Si está utilizando BackgroundWorker, debe plantear el evento en el mismo hilo de la interfaz de usuario.

Por ejemplo, si tienes dos vistas A y B y el siguiente código dentro de A plantea el evento WakeUpEvent

//Code inside codebehind or viewmodel of A var worker = new BackgroundWorker(); worker.DoWork += WorkerDoWork; //<-- Don''t raise the event WakeUpEvent inside this method worker.RunWorkerCompleted += workerRunWorkerCompleted; // <-- Raise the event WakeUpEvent inside this method instead worker.RunWorkerAsync(); //Code inside codebehind or viewmodel of view B public ViewB () { WakeUpEvent += UpdateUICallBack; } private void UpdateUICallBack() { //Update here UI element }

El método WorkerDoWork se ejecuta en un subproceso que no es el mismo de la interfaz de usuario.


Si no me equivoco, en WPF 4.5, debería poder hacer esto sin ningún problema.

Ahora para resolver esto, debes usar el contexto de sincronización. Antes de iniciar el hilo, debe almacenar el contexto de sincronización en el hilo de la interfaz de usuario.

var uiContext = SynchronizationContext.Current;

Entonces lo usas en tu hilo:

uiContext.Send(x => _matchObsCollection.Add(match), null);

Eche un vistazo a este tuto http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I