Ce type de CollectionView ne prend pas en charge les modifications apportées à son SourceCollection à partir d'un thread différent du thread du répartiteur

J'ai un DataGrid qui remplit les données de ViewModel par asynchrone method.My DataGrid est :

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

J'utilise http://www.amazedsaint.com/2010/10/asynchronous-delegate-command-for-your.html pour implémenter de manière asynchrone dans mon viewmodel.

Voici mon code 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);
            }

        }

Comme vous pouvez le voir dans ma méthode Load () dans mon ViewModel, je reçois d'abord matchList (qui est une liste d'une classe DataContract) de mon Service.Puis par foreach boucle je suis insérer mes éléments matchList dans ma _matchObsCollection (qui est une ObservableCollection de la classe DataContract)).Maintenant, ici, je reçois l'erreur ci-dessus (comme je l'ai montré dans Title) "ce type de CollectionView ne prend pas en charge les modifications apportées à son SourceCollection à partir d'un thread différent du thread du répartiteur" entrez la description de l'image ici

Quelqu'un Peut-il me suggérer une solution.De plus, si possible, je voudrais savoir comment lier mon DataGrid en vue et aussi l'actualiser de manière asynchrone si un meilleur moyen est là.

101
demandé sur wonea 2013-08-20 13:36:57

6 réponses

Puisque votre ObservableCollection est créée sur le thread de L'interface utilisateur, vous ne pouvez le modifier qu'à partir du thread de L'interface utilisateur et non à partir d'autres threads. Ceci est appelé affinité de thread .

Si vous avez besoin de mettre à jour des objets créés sur le thread de L'interface utilisateur à partir d'un thread différent, il suffit de put the delegate on UI Dispatcher et cela fonctionnera pour vous en le déléguant au thread de L'interface utilisateur. Cela fonctionnera -

    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);
            });
        }
    }
180
répondu Rohit Vats 2013-08-20 13:32:07

Si Je ne me trompe pas, dans WPF 4.5, vous devriez pouvoir le faire sans aucun problème.

Maintenant, pour résoudre ce problème, vous devez utiliser le contexte de synchronisation. Avant de lancer le thread, vous devez stocker le contexte de synchronisation dans le thread de l'interface utilisateur.

var uiContext = SynchronizationContext.Current;

Ensuite, vous l'utilisez dans votre fil:

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

Jetez un oeil à ce tuto http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I

45
répondu Daniel 2013-08-20 12:20:50

, Vous pouvez faire ceci:

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

Pour. Net 4.5+: vous pouvez suivre la réponse de Daniel. Dans son exemple, vous donnez la responsabilité à l'éditeur qu'il doit appeler ou invoquer sur le bon thread:

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

Ou vous pouvez mettre la responsabilité à votre service / viewmodel / whatever et simplement activer CollectionSynchronization. De cette façon, si vous faites un appel, vous n'avez pas à vous soucier de quel thread vous êtes et sur lequel vous faites l'appel. La responsabilité n'est pas pour le Éditeur plus. (cela peut vous donner un peu de surcharge de performance, mais en le faisant dans un service central, cela peut vous faire économiser beaucoup d'exceptions et vous faciliter la maintenance des applications.)

private static object _lock = new object();

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

Plus d'Informations: https://msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.enablecollectionsynchronization(v=vs.110).aspx

Dans Visual Studio 2015 (Pro) allez dans Debug - > Windows - > Threads pour déboguer facilement et voir sur quels threads vous sont sur.

35
répondu juFo 2017-11-30 09:29:01

J'ai rencontré le même problème une fois et résolu le problème avec AsyncObservableCollection ( http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/).

3
répondu mnyarar 2014-03-23 12:21:39

Dans mon cas (je remplis ObservableCollection avec des tâches asynchrones et n'ai pas accès à l'instance App) j'utilise TaskScheduler.FromCurrentSynchronizationContext() pour nettoyer la collection sur faulted:

        // 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());
2
répondu Vladislav 2014-10-24 08:39:20

Si vous utilisez BackgroundWorker, vous devez déclencher l'événement dans le même thread de l'interface utilisateur.

Par exemple, si vous avez deux vues A et B et que le code suivant dans A déclenche l'événement 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
    }

La méthode WorkerDoWork est exécutée dans un thread différent de L'interface utilisateur.

2
répondu Gianluca Conte 2017-06-02 15:01:14