Beim Aktualisieren einer Sammlung von Geschäftsobjekten in einem Hintergrundthread wird folgende Fehlermeldung angezeigt:
Diese Art von CollectionView unterstützt keine Änderungen an der SourceCollection von einem Thread, der sich vom Dispatcher-Thread unterscheidet.
Ok, das macht Sinn. Es stellt sich aber auch die Frage, welche Version von CollectionView mehrere Threads unterstützt und wie ich meine Objekte dazu bringe, sie zu verwenden.
.net
wpf
multithreading
thread-safety
collectionview
Jonathan Allen
quelle
quelle
Antworten:
Das Folgende ist eine Verbesserung gegenüber der von Jonathan gefundenen Implementierung. Zunächst wird jeder Ereignishandler auf dem ihm zugeordneten Dispatcher ausgeführt, anstatt davon auszugehen, dass sich alle auf demselben Dispatcher (UI) befinden. Zweitens wird BeginInvoke verwendet, damit die Verarbeitung fortgesetzt werden kann, während wir darauf warten, dass der Dispatcher verfügbar wird. Dies macht die Lösung in Situationen, in denen der Hintergrund-Thread viele Aktualisierungen mit der Verarbeitung zwischen den einzelnen Threads durchführt, viel schneller. Vielleicht noch wichtiger ist, dass es Probleme überwindet, die durch Blockieren während des Wartens auf den Aufruf verursacht werden (Deadlocks können beispielsweise auftreten, wenn WCF mit ConcurrencyMode.Single verwendet wird).
public class MTObservableCollection<T> : ObservableCollection<T> { public override event NotifyCollectionChangedEventHandler CollectionChanged; protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { NotifyCollectionChangedEventHandler CollectionChanged = this.CollectionChanged; if (CollectionChanged != null) foreach (NotifyCollectionChangedEventHandler nh in CollectionChanged.GetInvocationList()) { DispatcherObject dispObj = nh.Target as DispatcherObject; if (dispObj != null) { Dispatcher dispatcher = dispObj.Dispatcher; if (dispatcher != null && !dispatcher.CheckAccess()) { dispatcher.BeginInvoke( (Action)(() => nh.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))), DispatcherPriority.DataBind); continue; } } nh.Invoke(this, e); } } }
Da wir BeginInvoke verwenden, ist es möglich, dass die zu benachrichtigende Änderung rückgängig gemacht wird, bevor der Handler aufgerufen wird. Dies würde normalerweise zu einem "Index außerhalb des Bereichs" führen. Ausnahme wird ausgelöst, wenn die Ereignisargumente mit dem neuen (geänderten) Status der Liste verglichen werden. Um dies zu vermeiden, werden alle verzögerten Ereignisse durch Reset-Ereignisse ersetzt. Dies kann in einigen Fällen zu übermäßigem Neuzeichnen führen.
quelle
Verwenden:
System.Windows.Application.Current.Dispatcher.Invoke( System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate() { // Your Action Code });
quelle
Invoke
Ergebnissen beim Einfrieren der Benutzeroberfläche. Verwenden SieBeginInvoke
stattdessen.Dieser Beitrag von Bea Stollnitz erklärt diese Fehlermeldung und warum sie so formuliert ist, wie sie ist.
EDIT: Aus Bea's Blog
quelle
Ich habe einen gefunden.
public class MTObservableCollection<T> : ObservableCollection<T> { public override event NotifyCollectionChangedEventHandler CollectionChanged; protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { var eh = CollectionChanged; if (eh != null) { Dispatcher dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList() let dpo = nh.Target as DispatcherObject where dpo != null select dpo.Dispatcher).FirstOrDefault(); if (dispatcher != null && dispatcher.CheckAccess() == false) { dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e))); } else { foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()) nh.Invoke(this, e); } } } }
http://www.julmar.com/blog/mark/2009/04/01/AddingToAnObservableCollectionFromABackgroundThread.aspx
quelle
dispatcher.Invoke
, dh etwas im GUI-Thread. Dies bedeutet zwei Dinge: 1. Der Worker-Thread muss jedes Mal anhalten und auf den GUI-Thread warten, wenn er der Sammlung etwas hinzufügt. Das Wechseln von Aufgaben ist teuer und verringert die Leistung. 2. Der GUI-Thread drosselt möglicherweise den Arbeitsaufwand, der zu einer nicht reagierenden GUI führt. Eine zeitgesteuerte Lösung für ein ähnliches Problem finden Sie hier stackoverflow.com/a/4530900/157224 .Sie können sich auch ansehen :
BindingOperations.EnableCollectionSynchronization
.Siehe Upgrade auf .NET 4.5: Ein ItemsControl ist nicht mit seiner Elementquelle inkonsistent
quelle
Entschuldigung, ich kann keinen Kommentar hinzufügen, aber das ist alles falsch.
ObservableCollection ist nicht threadsicher. Nicht nur wegen dieser Dispatcher-Probleme, sondern es ist überhaupt nicht threadsicher (von msdn):
Schauen Sie hier http://msdn.microsoft.com/en-us/library/ms668604(v=vs.110).aspx
Es gibt auch ein Problem beim Aufrufen von BeginInvoke mit einer Aktion "Zurücksetzen". "Zurücksetzen" ist die einzige Aktion, bei der der Handler die Sammlung selbst anzeigen sollte. Wenn Sie ein "Zurücksetzen" beginnen und dann sofort ein paar "Hinzufügen" -Aktionen starten, akzeptiert der Handler ein "Zurücksetzen" mit bereits aktualisierter Sammlung, und das nächste "Hinzufügen" führt zu einem Durcheinander.
Hier ist meine Implementierung, die funktioniert. Eigentlich denke ich daran, BeginInvoke überhaupt zu entfernen:
Schnelle und threadsichere beobachtbare Sammlung
quelle
Sie können wpf veranlassen, threadübergreifende Änderungen an einer Sammlung zu verwalten, indem Sie die Synchronisierung der Sammlung wie folgt aktivieren:
BindingOperations.EnableCollectionSynchronization(collection, syncLock); listBox.ItemsSource = collection;
Dies teilt WPF mit, dass die Sammlung möglicherweise außerhalb des UI-Threads geändert wird, sodass bekannt ist, dass alle UI-Änderungen wieder in den entsprechenden Thread zurückgeführt werden müssen.
Es gibt auch eine Überlastung, um einen Synchronisationsrückruf bereitzustellen, wenn Sie kein Sperrobjekt haben.
quelle
Wenn Sie die WPF-Benutzeroberflächensteuerung regelmäßig aktualisieren und gleichzeitig die Benutzeroberfläche verwenden möchten, können Sie DispatcherTimer verwenden .
XAML
<Grid> <DataGrid AutoGenerateColumns="True" Height="200" HorizontalAlignment="Left" Name="dgDownloads" VerticalAlignment="Top" Width="548" /> <Label Content="" Height="28" HorizontalAlignment="Left" Margin="0,221,0,0" Name="lblFileCouner" VerticalAlignment="Top" Width="173" /> </Grid>
C #
public partial class DownloadStats : Window { private MainWindow _parent; DispatcherTimer timer = new DispatcherTimer(); ObservableCollection<FileView> fileViewList = new ObservableCollection<FileView>(); public DownloadStats(MainWindow parent) { InitializeComponent(); _parent = parent; Owner = parent; timer.Interval = new TimeSpan(0, 0, 1); timer.Tick += new EventHandler(timer_Tick); timer.Start(); } void timer_Tick(object sender, EventArgs e) { dgDownloads.ItemsSource = null; fileViewList.Clear(); if (_parent.contentManagerWorkArea.Count > 0) { foreach (var item in _parent.contentManagerWorkArea) { FileView nf = item.Value.FileView; fileViewList.Add(nf); } } if (fileViewList.Count > 0) { lblFileCouner.Content = fileViewList.Count; dgDownloads.ItemsSource = fileViewList; } } }
quelle
Versuche dies:
this.Dispatcher.Invoke(DispatcherPriority.Background, new Action( () => { //Code }));
quelle
Keiner von ihnen, verwenden Sie einfach Dispatcher.BeginInvoke
quelle
Hier ist eine VB-Version, die ich nach einigem googeln und leichten Mods gemacht habe. Funktioniert bei mir.
Imports System.Collections.ObjectModel Imports System.Collections.Specialized Imports System.ComponentModel Imports System.Reflection Imports System.Windows.Threading 'from: http://stackoverflow.com/questions/2137769/where-do-i-get-a-thread-safe-collectionview Public Class ThreadSafeObservableCollection(Of T) Inherits ObservableCollection(Of T) 'from: http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/listcollectionviewcollectionview-doesnt-support-notifycollectionchanged-with-multiple-items.aspx Protected Overrides Sub OnCollectionChanged(ByVal e As System.Collections.Specialized.NotifyCollectionChangedEventArgs) Dim doit As Boolean = False doit = (e.NewItems IsNot Nothing) AndAlso (e.NewItems.Count > 0) doit = doit OrElse ((e.OldItems IsNot Nothing) AndAlso (e.OldItems.Count > 0)) If (doit) Then Dim handler As NotifyCollectionChangedEventHandler = GetType(ObservableCollection(Of T)).GetField("CollectionChanged", BindingFlags.Instance Or BindingFlags.NonPublic).GetValue(Me) If (handler Is Nothing) Then Return End If For Each invocation As NotifyCollectionChangedEventHandler In handler.GetInvocationList Dim obj As DispatcherObject = invocation.Target If (obj IsNot Nothing) Then Dim disp As Dispatcher = obj.Dispatcher If (disp IsNot Nothing AndAlso Not (disp.CheckAccess())) Then disp.BeginInvoke( Sub() invocation.Invoke(Me, New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)) End Sub, DispatcherPriority.DataBind) Continue For End If End If invocation.Invoke(Me, e) Next End If End Sub End Class
quelle
Kleiner Fehler in der VB-Version. Einfach ersetzen:
Dim obj As DispatcherObject = invocation.Target
Durch
Dim obj As DispatcherObject = TryCast(invocation.Target, DispatcherObject)
quelle