Entwickler-Ecke

WinForms - Formular trotz Multithreading nicht bedienbar durch Invokes


AXMD - Di 15.04.08 18:41
Titel: Formular trotz Multithreading nicht bedienbar durch Invokes
Hallo!

Ich beschäftige mich seit kurzem mit Multithreading in .NET und habe testweise ein Programm geschrieben, das Dateien in einem separaten Thread rekursiv in einem vorgegebenen Ordner sucht und das Ergebnis in einer ListBox ausgibt. Hier der relevante Codeausschnitt des Threads:


C#-Quelltext
1:
2:
3:
4:
5:
6:
                FilesFoundListBox.Invoke(new VoidCall(FilesFoundListBox.BeginUpdate));
                FilesFoundListBox.Invoke(new VoidCall(FilesFoundListBox.Items.Clear));
                SetText(FilesFoundGroupBox, "Files found");
                UnfilteredFileList = ... //Dateisuche (der Übersicht halber entfernt)
                FilesFoundListBox.Invoke(new ObjectArrayParamCall(FilesFoundListBox.Items.AddRange), new object[] { UnfilteredFileList });
                FilesFoundListBox.Invoke(new VoidCall(FilesFoundListBox.EndUpdate));


wobei ich für die Invoke-Aufrufe noch diese beiden Delegates deklariert habe:


C#-Quelltext
1:
2:
        delegate void VoidCall();
        delegate void ObjectArrayParamCall(object[] Param);


Wenn ich jetzt beispielsweise C:\ durchsuchen lasse, dauert das sehr lange, aber ich kann während die Suche läuft das Formular wie gewohnt bedienen. Sobald allerdings FilesFoundListBox.Items.AddRange aufgerufen wird, reagiert das Hauptformular nicht mehr und wird für ca. eine Minute weiß. Da ich Invokes verwende ist das prinzipiell logisch, da der Hauptthread AddRange ausführt.
Nun meine Frage: wie kann ich dennoch dafür sorgen, dass mein Formular während des Hinzufügens der Items in die ListBox bedienbar bleibt? Gibt es eventuell Alternativlösungen?

AXMD


Kha - Mi 16.04.08 10:27

Na dann würde ich einfach die Items einzeln hinzufügen ;) . Dann lässt sich (ohne Begin/EndUpdate) auch das (flackernde) Befüllen beobachten. Irgendwann werden aber die tausend Invoke-Aufrufe selbst natürlich zum Flaschenhals, man könnte also stattdessen z.B. immer 10 Items auf einmal zur ListBox schicken. Oder im Nebenthread eine Queue befüllen und diese dann im Hauptthread mit einem 100ms-Timer abarbeiten... naja, wir wollen mal nicht übertreiben :mrgreen: .

PS: Vielleicht ist hier BeginInvoke (asynchron) besser geeignet, damit wartet der Such-Thread nicht mehr auf die GUI-Aktionen in den Invokes.


AXMD - Mi 16.04.08 11:27

user profile iconKhabarakh hat folgendes geschrieben:
Na dann würde ich einfach die Items einzeln hinzufügen ;) .

Das erhöht doch die Dauer zusätzlich

user profile iconKhabarakh hat folgendes geschrieben:
Dann lässt sich (ohne Begin/EndUpdate) auch das (flackernde) Befüllen beobachten.

Das halte ich nicht für sinnvoll, vor allem weil das Flackern störend ist und unnötig viel Zeit kostet

user profile iconKhabarakh hat folgendes geschrieben:
Irgendwann werden aber die tausend Invoke-Aufrufe selbst natürlich zum Flaschenhals, man könnte also stattdessen z.B. immer 10 Items auf einmal zur ListBox schicken.

Das ändert aber auch nichts an der Tatsache, dass das Formular in der Zwischenzeit nicht reagiert

user profile iconKhabarakh hat folgendes geschrieben:
Vielleicht ist hier BeginInvoke (asynchron) besser geeignet, damit wartet der Such-Thread nicht mehr auf die GUI-Aktionen in den Invokes.

Es spielt keine Rolle, ob der Nebenthread wartet oder nicht - es geht darum, dass das Hauptformualar bedienbar bleibt.

AXMD


Christian S. - Mi 16.04.08 11:40

user profile iconAXMD hat folgendes geschrieben:
user profile iconKhabarakh hat folgendes geschrieben:
Na dann würde ich einfach die Items einzeln hinzufügen ;) .

Das erhöht doch die Dauer zusätzlich

Wenn Du eine Aktualisierung der GUI willst, braucht das nunmal Rechenzeit. Eine eingefrorene GUI wird immer weniger Rechenzeit brauchen, als eine, die reagiert. Erinnere Dich an Application.ProcessMessages aus den Delphi-Zeiten ;-)

Du schreibst zwar, die GUI hänge bei AddRange, aber es dürfte eher der Aufruf von EndUpdate sein, oder? Denn erst dann wird die GUI aktualisiert. Und zu dem Zeitpunkt wird halt ein großer Batzen an Items auf einmal in die Listbox gepackt und das blockiert. Ich sehe auch nur die Möglichkeit, immer nur ein paar Items in die Listbox zu packen , damit die GUI zwischendurch Zeit hat, sich zu Aktualisieren.

In der WPF hätte man die Möglichkeit, bei Invoke noch eine Priorität mitzugeben, aber das ist mir aus WinForms nicht bekannt.


AXMD - Mi 16.04.08 12:27

Dann werde ich heute Abend mal versuchen, die Listbox 10-Items-weise zu befüllen. Alternativ schwebt mir eine ListView vor (ist die eventuell schneller?) - habe aber hier in der Arbeit keine Zeit, um das zu testen.

Danke für deine Antwort
AXMD