it-swarm.com.de

Dieser CollectionView-Typ unterstützt keine Änderungen an seiner SourceCollection von einem anderen Thread als dem Dispatcher-Thread

Ich habe ein DataGrid, das Daten aus ViewModel mit einer asynchronen Methode auffüllt. Mein DataGrid ist:

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

Ich verwende http://www.amazedsaint.com/2010/10/asynchronous-delegate-command-for-your.html , um einen asynchronen Weg in meinem Ansichtsmodell zu implementieren.

Hier ist mein Viewmodel-Code:

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);
            }

        }

Wie Sie in meiner Load () - Methode in meinem ViewModel zuerst sehen können, erhalte ich matchList (eine Liste einer DataContract-Klasse) von meinem Service. Dann füge ich per foreach-Schleife meine matchList-Elemente in meine _matchObsCollection (eine ObservableCollection) ein der DataContract-Klasse)). Jetzt erhalte ich den obigen Fehler (wie in Title gezeigt) "Dieser CollectionView-Typ unterstützt keine Änderungen an seiner SourceCollection von einem anderen Thread als dem Dispatcher-Thread" enter image description here

Kann mir jemand eine Lösung vorschlagen. Darüber hinaus würde ich gerne wissen, wie ich mein DataGrid in View binden und es auch asynchron auffrischen kann, wenn es einen besseren Weg gibt.

111

Da Ihre ObservableCollection im UI-Thread erstellt wird, können Sie sie nur vom UI-Thread und nicht von anderen Threads ändern. Dies wird als Thread-Affinität bezeichnet.

Wenn Sie jemals Objekte aktualisieren müssen, die auf einem UI-Thread aus einem anderen Thread erstellt wurden, müssen Sie einfach put the delegate on UI Dispatcher eingeben, und dies erledigt die Delegierung an den UI-Thread. Das wird funktionieren -

    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);
            });
        }
    }
194
Rohit Vats

Wenn ich mich nicht irre, sollten Sie dies in WPF 4.5 problemlos tun können.

Um dies zu lösen, sollten Sie den Synchronisationskontext verwenden. Bevor Sie den Thread starten, müssen Sie den Synchronisationskontext im UI-Thread speichern.

var uiContext = SynchronizationContext.Current;

Dann benutzt du es in deinem Thread:

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

Sehen Sie sich dieses Tuto an http://www.codeproject.com/Articles/31971/Unterstanding-SynchronizationContext-Part-I

51
Daniel

Du kannst das:

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

Für .NET 4.5+: Sie können der Antwort von Daniel folgen. In seinem Beispiel geben Sie dem Publisher die Verantwortung, die er zum Aufrufen oder Aufrufen des richtigen Threads benötigt:

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

Oder Sie können die Verantwortung auf Ihren Dienst/Ihr Ansichtsmodell/irgendetwas legen und einfach CollectionSynchronization aktivieren. Auf diese Weise müssen Sie sich beim Tätigen eines Anrufs keine Gedanken darüber machen, auf welchem ​​Thread Sie sich befinden und auf welchem ​​Sie den Anruf tätigen. Die Verantwortung liegt nicht mehr beim Verlag. (Dies kann zu einem geringfügigen Performance-Overhead führen, aber dies in einem zentralen Dienst zu tun, kann Ihnen viele Ausnahmen ersparen und die Anwendungswartung vereinfachen.)

private static object _lock = new object();

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

Weitere Informationen: https://msdn.Microsoft.com/en-us/library/system.windows.data.bindingoperations.enablecollectionsynchronization (v = vs.110) .aspx

Wechseln Sie in Visual Studio 2015 (Pro) zu Debug -> Windows -> Threads , um das Debuggen zu vereinfachen und festzustellen, auf welchen Threads Sie sich befinden.

45
juFo

Ich habe das gleiche Problem einmal erlebt und das Problem mit AsyncObservableCollection gelöst ( http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asasynchronouscollection/ ).

3
mnyarar

Wenn Sie BackgroundWorker verwenden, sollten Sie das Ereignis im selben Thread der Benutzeroberfläche auslösen.

Wenn Sie also zwei Ansichten A und B haben und der folgende Code innerhalb von A den Event WakeUpEvent auslöst

//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
    }

Die WorkerDoWork-Methode wird in einem Thread ausgeführt, der nicht mit der Benutzeroberfläche identisch ist.

2
Gianluca Conte

In meinem Fall (ich fülle ObservableCollection mit asynchronen Tasks auf und habe keinen Zugriff auf App-Instanz) verwende ich TaskScheduler.FromCurrentSynchronizationContext(), um die Sammlung bei fehlerhaften Daten zu bereinigen:

        // 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
Vladislav

Ich habe hier eine Lösung gefunden: https://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/ Sie erstellen gerade eine neue Klasse und verwenden Sie es statt ObservableCollection. Es hat für mich funktioniert.

public class AsyncObservableCollection<T> : ObservableCollection<T>
{
    private SynchronizationContext _synchronizationContext = SynchronizationContext.Current;

    public AsyncObservableCollection()
    {
    }

    public AsyncObservableCollection(IEnumerable<T> list)
        : base(list)
    {
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (SynchronizationContext.Current == _synchronizationContext)
        {
            // Execute the CollectionChanged event on the current thread
            RaiseCollectionChanged(e);
        }
        else
        {
            // Raises the CollectionChanged event on the creator thread
            _synchronizationContext.Send(RaiseCollectionChanged, e);
        }
    }

    private void RaiseCollectionChanged(object param)
    {
        // We are in the creator thread, call the base implementation directly
        base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param);
    }

    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if (SynchronizationContext.Current == _synchronizationContext)
        {
            // Execute the PropertyChanged event on the current thread
            RaisePropertyChanged(e);
        }
        else
        {
            // Raises the PropertyChanged event on the creator thread
            _synchronizationContext.Send(RaisePropertyChanged, e);
        }
    }

    private void RaisePropertyChanged(object param)
    {
        // We are in the creator thread, call the base implementation directly
        base.OnPropertyChanged((PropertyChangedEventArgs)param);
    }
}
1
Istvan Heckl

Ich habe auch diese Fehlermeldung erhalten:

"Dieser CollectionView-Typ unterstützt keine Änderungen an seiner SourceCollection aus einem anderen Thread als dem Dispatcher-Thread."

Es stellte sich heraus, dass ich eine neue Konfiguration namens "Release Android" erstellt hatte, die eine Kopie der "Release" -Konfiguration war und diese zum Erstellen der neuen Version im Archiv-Manager verwendete. Ich habe wieder auf die Konfiguration "Release" umgestellt und alles gut gebaut. Kein Fehler mehr.

Hoffe das hilft jemandem.

0
Shane