| 

.NET C# Java Javascript Exception

7
Hallo
ich habe folgendes Problem. Ein BackgroundWorker soll mir Items in eine ObservableCollection in meinem ViewModel schreiben.

Ich bekomme aber immer diese Meldung:
Von diesem CollectionView-Typ werden keine Änderungen der "SourceCollection" unterstützt, wenn diese nicht von einem Dispatcher-Thread aus erfolgen.


Kann mir da jemand helfen, wie ich hier am besten vorgehen muss um die Collection auch aus einem anderen Thread befüllen zu können?

Update:
Hier noch mein Code.

void bw_DoWork(object sender, DoWorkEventArgs e)
{
var ds = new DataSet();
var dv = new DataView();
var i = 1;

ds = Utils.getWebService().searchDataSet(CurrentMode, Keyword, "", false);
dv = ds.Tables[0].DefaultView;

SetPersons(dv);
}


private void SetPersons(DataView dv)
{
int i = 0;
Person person;

Persons.Clear();

ListCollectionView tmp = new ListCollectionView(Persons);

ResultPersons = tmp;

foreach (DataRowView drv in dv)
{
person = new Person();

person.PersonContactId = Convert.ToInt32(drv.Row.ItemArray[0] == null ? "" : drv.Row.ItemArray[0].ToString());
person.CompanyId = Convert.ToInt32(drv.Row.ItemArray[1] == null ? "" : drv.Row.ItemArray[1].ToString());

_bw.ReportProgress(0, person);
}
}


void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//Hier sollte die Zuweisung zu meiner ObservableCollection erfolgen
//Persons.Add(e.UserState as Person);
}
15.03.2013
mrmee 745 1 8
mrmee 745 1 8
3 Antworten
4
Hi,

lies Dir mal das hier durch: How to: Use a Background Worker.

Das wichtigste vereinfacht in Kürze:

  • Du kannst UI-Elemente nur von dem Thread aus manipulieren, von dem aus sie erzeugt wurden. Andernfalls bekommst Du die von Dir beobachtete Exception.
  • Dispatcher.Invoke() ist dazu da, von einem "Nicht-UI-Thread" wieder in den UI-Thread (Dispatcher-Thread) zu wechseln.
  • Die BackgroundWorker-Klasse ist dazu da, sich bei bestimmungsgemäßem Gebrauch um all das nicht kümmern zu müssen. D.h. wenn Du mit dem BackgroundWorker auf ein Problem stößt, das sich scheinbar durch Dispatcher.Invoke() lösen läßt, programmierst Du wahrscheinlich um einen anderen Fehler herum.

Wenn ich mir Deinen Code anschaue, so scheint mir, dass SetPersons() von RunWorkerCompleted() aufgerufen werden sollte, nicht aus DoWork() heraus - die zeitintensive Arbeit ist ja vermutlich eher das Laden der Daten vom WebService als die Befüllung des UIs. Und ReportProgress() ist wirklich nur dazu da, einen "prozentualen" Fortschritt zu berichten und nicht, um Business-Objekte wie Person zu transportieren. Es kann dann sinnvoll eingesetzt werden, wenn die Arbeit in DoWork sich in sinnvolle Teilaufgaben gliedern läßt.
15.03.2013
Matthias Hlawatsch 13,2k 4 9
Da die Oberfläche "einfriert" während ich die ObservableCollection in SetPersons befülle wollte ich ein "partielles laden" implementieren.
mrmee 15.03.2013
D.h. das reine Befüllen der ObservableCollection dauert inakzeptabel lange? Dann solltest Du mal nachschauen (oder hier fragen - ich selbst kenne mich da zu wenig aus), ob da nicht noch irgendwo eine Handbremse gezogen ist. Ansonsten wäre eine Möglichkeit, alle paar Datensätze dem UI mal wieder die Möglichkeit zu geben, was anderes zu tun. Unter Windows Forms gibt es dafür Application.DoEvents(). Zu WPF schau Dir mal die dritte Antwort bei
http://stackoverflow.com/questions/4502037/where-is-the-application-doevents-in-wpf an (die mit Dispatcher.PushFrame()).
Matthias Hlawatsch 15.03.2013
Ja genau das ist mein Problem. Es kommt mir nur seltsam vor, dass 1000 Treffer bereits ein Problem für das DataGrid darstellen.
mrmee 15.03.2013
Wie wäre es mit

http://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/

oder weitere Google-Treffer zu "observablecollection performance" ?
Matthias Hlawatsch 15.03.2013
Mein Problem ist nicht das ObservableCollection.Add(). SetPersons() ist schon lange fertig bis meine UI wieder verwendet werden kann. Es muss am DataGrid liegen.
mrmee 15.03.2013
1
Ich würde das nicht unterschätzen - immerhin generiert die ObservableCollection einen Haufen Events, die das DataGrid verarbeiten muß. Ansonsten schau halt entsprechend mal nach entsprechenden DataGrid-Problemen. Da scheint es auch ein paar zu geben, siehe z.B. http://stackoverflow.com/questions/3336921/unreasonable-wpf-datagrid-loading-time
Matthias Hlawatsch 15.03.2013
Der Link hört sich interessant an.
mrmee 15.03.2013
2
Mit dem Dispatcher sollte das funktionieren.

Dispatcher.CurrentDispatcher.Invoke((Action)(() => 
{
// Hier die Zuweisung zur ObservableCollection
}));


EDIT: Der Code wird hier im ViewModel aufgerufen. Wenn Du dies direkt im View machst, dann

Dispatcher.Invoke((Action)(() => 
{
// Hier die Zuweisung zur ObservableCollection
}));
15.03.2013
lbm1305 849 1 8
lbm1305 849 1 8
Kann ich das im ProgressChanged Event machen? Hatte das vorhin mal getestet und es hat nicht geklappt.
mrmee 15.03.2013
Eher DoWork()
lbm1305 15.03.2013
1
Sorry...RunWorkerCompleted()
lbm1305 15.03.2013
Danke für deine Hilfe. Im RunWorkerCompleted() funktioniert es. Würde es aber auch gerne im ProgressChanged() können um partielles laden zu ermöglichen. Gibt es da eine Möglichkeit? (Hab mal meine Frage um meinen Code ergänzt)
mrmee 15.03.2013
Ähm, es stimmt zwar, dass die Exception durch einen UI-Zugriff aus dem falschen Thread heraus verursacht wird und dass Dispatcher.Invoke() ein Mittel ist, den Aufruf in den richtigen Thread zu verschieben - aber der BackgroundWorker ist ja gerade dazu da, dass man diese Thread-Wechsel nicht selbst programmieren muß. Das hier sollte also nicht die Lösung sein.
Matthias Hlawatsch 15.03.2013
2
Hi,

es sollte möglich sein, die Personen im EventHandler für das BackgroundWorker.ProgressChanged Event in die ObservableCollection einzuhängen, das der EventHandler im UI-Thread ausgeführt wird. Ich vermute, dass die Exception von dem Aufruf an Persons.Clear() (in der SetPersons Methode) geworfen wird, da diese im Context des Worker(!)-Threads aufgerufen wird.

Was die Performance generell angeht solltest du überlegen, ob Du wirklich ein DataGrid brauchst. Musst du wirklich inline editieren? In den meisten Fällen tut's auch eine ListBox oder ListView (spaltenorientiert). Da die ListBox per Default ein VirtualizingStackPanel als Container für die Items verwendet, ist das Einfügen rasend schnell und unabhängig von der Datenmenge.

Ansonsten erscheint mir dein Design generell verdächtig. Was lange dauert, ist vermutlich der Aufruf an den WebService (searchDataSet). Das Konvertieren des DataSets in Person-Objekte sollte unmerklich schnell gehen. Insofern lohnt es sich nicht, das im Worker-Thread zu machen und jedes einzelne Person-Objekt über ReportProgress in den UI-Thread zu transportieren. Da verlierst du durch die ständigen Thread-Context-Wechsel unnötig Zeit.

Good luck,
Chris
15.03.2013
candritzky 66 1
An ein anderes Steuerelement hab ich auch schon gedacht. Inline editieren ist nicht zwingend notwendig. Allerdings sollte man filtern, sortieren und gruppieren können, was mich eben zum DataGrid gebracht hat. Ich weiß dass das eine ListView auch kann. Werde mal testen ob es performanter ist, wenn ich ein anderes Steuerelement verwende. Der Aufruf des Webservice ist im Vergleich zum anzeigen der Daten "pfeil schnell". Danke für deine Hilfe!
mrmee 15.03.2013
So, ich hab mir jetzt die ListView angesehen und gefällt mir auch ganz gut. HAt mich aber jetzt zu folgender Frage geführt. http://codekicker.de/fragen/WPF-GridViewRowPresenter-IsMouseOver-austauschen
mrmee 19.03.2013

Stelle deine .net-Frage jetzt!
TOP TECHNOLOGIES CONSULTING GmbH