myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
» Regeln
» Wie poste ich richtig?
» Forum-FAQ

Mitglieder
» Liste / Suche
» Wer ist wo online?

Ressourcen
» openbook: Visual C#
» openbook: OO
» Microsoft Docs

Team
» Kontakt
» Übersicht
» Wir über uns

» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Entwicklung » GUI: Windows-Forms » Eleganteste Art aus Worker-Thread auf Controls zugreifen [generell Kontrollfluss zwischen Threads]
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

Eleganteste Art aus Worker-Thread auf Controls zugreifen [generell Kontrollfluss zwischen Threads]

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
myCSharp.de
Moderationshinweis von herbivore (03.06.2010 07:47):

Dies ist ein Thread, auf den aus der FAQ verwiesen wird. Bitte keine weitere Diskussion, sondern nur wichtige Ergänzungen und diese bitte knapp und präzise. Vielen Dank!
 
moson moson ist männlich
myCSharp.de-Mitglied

avatar-7.jpg


Dabei seit: 20.11.2003
Beiträge: 151
Entwicklungsumgebung: Visual Studio 2015 Community


moson ist offline

Eleganteste Art aus Worker-Thread auf Controls zugreifen [generell Kontrollfluss zwischen Threads]

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Mahlzeit,

ich hab mal ne Frage und zwar hab ich eine Form welche ein Object o von Klasse Klasse instanziert. Dann wird bei einem Buttonklick eine Methode von o aufgerufen. In der Methode wird nun ein neuer Thread erstellt, aus diesem Thread heraus sollen Eigenschaften von Controls auf dem Form verändert werden.

In Code ausgedrückt:

C#-Code:
public partial class Form1 : Form
{
        ...
        private void button1_Click(object sender, EventArgs e)
        {
                Klasse o = new Klasse("blablubb");
                o.DoFunkyStuff();
        }
        ...
}
class Klasse
{
        public void DoFunkyStuff()
        {
                 ...
                 Thread th = new Thread(new ThreadStart(DoOtherFunkyStuff));
                 th.Start();
                 ...
        }
        private void DoOtherFunkyStuff()
        {
                 ...
                 //Hier Zugriff auf Form1 Controls... | Feuern des Events...
                 ...
        }
}

Ich habe das jetzt erstmal über ein Event welches ich im Form abboniert habe + Form.Invoke gelöst.

C#-Code:
public delegate void myDelegate(string a, string b, int x);

private void StatusChanged(string a, string b, int x) //Methode die beim Auslösen des Events gefeuert wird
{
       this.Invoke(new myDelegate(updateGUI), a, b, x);
}
private void updateGUI(string a, string b, int x)
{
       Control1.Text = a;
       Control2.Text = b;
       Control3.Value = x;
}

Ist die Vorgehensweise so in Ordnung oder gibts da bessere/schönere Ansätze?

Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von moson am 24.06.2009 20:39.

24.06.2009 20:33 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
tom-essen tom-essen ist männlich
myCSharp.de-Poweruser/ Experte

avatar-2140.png


Dabei seit: 15.05.2005
Beiträge: 1.815
Entwicklungsumgebung: VS.NET 2013
Herkunft: NRW


tom-essen ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo!

Man kann sich die Deklaration eines Delegaten sparen, wenn man anonyme Methoden verwendet:

C#-Code:
control.BeginInvoke((MethodInvoker)delegate
    {
        // Do something with control
    }
);
24.06.2009 22:49 Beiträge des Benutzers | zu Buddylist hinzufügen
JunkyXL JunkyXL ist männlich
myCSharp.de-Poweruser/ Experte

avatar-3234.gif


Dabei seit: 02.05.2006
Beiträge: 1.665
Entwicklungsumgebung: Visual Studio 2010 Ultimate
Herkunft: Ein paar Bytes südlich von string


JunkyXL ist offline Füge JunkyXL Deiner Kontaktliste hinzu

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top


mycsharp.de  Moderationshinweis von herbivore (16.03.2011 10:51):

Ursprünglich gepostet am 25.06.2009 11:01:10. Postzeit geändert, um den Beitrag an diese Stelle im Thread zu verschieben, direkt hinter den Beitrag, auf den sich die Antwort bezieht.
 

Was aber wiederum Edit & Continue beim Debuggen unmöglich macht :-)
24.06.2009 22:49 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Poweruser/ Experte

avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.478
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo moson,

Zitat:
Ist die Vorgehensweise so in Ordnung oder gibts da bessere/schönere Ansätze?

ja, das ist eine sinnvolle Aufgabenteilung und insofern vollkommen in Ordnung.

Natürlich könntest du beim Auslösen des Events intern SynchronizationContext.Send oder Post verwenden, damit die EventHandler im GUI-Thread laufen, wie das auch bei den BackgroundWorker-Events passiert. Diese Aufgabenteilung ist weniger sinnvoll, aber dafür etwas bequemer, inbesondere wenn ungeübte Programmierer die Klasse benutzen wollen.

Mein Fazit ist trotzdem: besser und richtiger ist es, wie du es jetzt hast.

herbivore
25.06.2009 00:58 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
gfoidl gfoidl ist männlich
myCSharp.de-Team

avatar-2894.jpg


Dabei seit: 07.06.2009
Beiträge: 6.659
Entwicklungsumgebung: VS 2019
Herkunft: Waidring


gfoidl ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo herbivore,

kannst du das bitte begründen:

Zitat:
Natürlich könntest du beim Auslösen des Events intern SynchronizationContext.Send oder Post verwenden, damit die EventHandler im GUI-Thread laufen, wie das auch bei den BackgroundWorker-Events passiert. Diese Aufgabenteilung ist weniger sinnvoll,

Ich finde es passender wenn der Aufrufer nichts zusätzliches implementieren muss - für mich bedeutet das auch Kapselung. Dem Aufrufer kann es ja egal sein ob die Methode die er ausführen lässt im gleichen Thread läuft oder in einem anderen. Er sollte gar nicht mitbekommen was in der Methode passiert - hauptsache es geschieht das was passieren soll.

Der Aufwand einen SynchronizationContext oder eine AsyncOperation zu implementieren ist (für mich) geringer als für jeden EventHandler ein Invoke zu schreiben.

Oder sehe ich da etwas falsch?

Vielen Dank im Voraus für die Antwort(en).

mfG Gü
25.06.2009 02:40 Beiträge des Benutzers | zu Buddylist hinzufügen
kleines_eichhoernchen kleines_eichhoernchen ist männlich
myCSharp.de-Mitglied

avatar-2079.jpg


Dabei seit: 07.11.2006
Beiträge: 3.971
Entwicklungsumgebung: Visual Studio 2005 (C#)
Herkunft: Ursprünglich Vogtland, jetzt Much


kleines_eichhoernchen ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo moson,
Verwende bitte bei kleinen asynchronen Aufgaben ThreadPool.QueueUserWorkItem statt jedesmal aufwändig einen eigenen Thread dafür zu erstellen.

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von kleines_eichhoernchen am 25.06.2009 07:35.

25.06.2009 07:35 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Poweruser/ Experte

avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.478
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top


mycsharp.de  Moderationshinweis von herbivore (25.09.2011 09:03):

In diesem Beitrag geht es, ausgehend von einem Szenario, in dem die Ausführung des Code in EventHandlern an den GUI-Thread delegieren werden muss, um die Frage, ob das dafür nötige Control.Invoke, schon beim Auslösen eines Events aufrufen soll, so dass alle registrieren EventHandler automatisch im GUI-Thread laufen, oder ob Control.Invoke erst in den EventHandlern aufgerufen werden soll.

Um die Frage zu beantworten, werden einige sehr prinzipielle Überlegungen über Zuständigkeiten angestellt. Die kurze Antwort ist, dass erst die EventHandler wissen, was zu tun ist und daher auch erst diese bei Bedarf das Control.Invoke aufrufen sollten.

Bezogen auf auf die Frage, was man tun soll, wenn man in einem Worker-Thread kein Control zur Verfügung hat, um Control.Invoke aufrufen zu können, bedeutet das, dass man in dem Thread ein  [FAQ] eigenen Event definieren und feuern sollte, der vom GUI abonniert wird. Im Ergebnis liegt der EventHandler in einer GUI-Klasse und dort ist dann automatisch ein passendes Control für das Control.Invoke verfügbar.

Natürlich kann man gedanklich noch einen Schritt weiter gehen: Wenn sich die Notwendigkeit für einen Worker-Thread aus der Anforderung ergibt, dass das GUI nicht blockieren soll, dann ist der Thread selbst Sache des GUIs (auch wenn aus dem Thread heraus Methoden der Modellklassen aufgerufen werden). Im Ergebnis gehört die ThreadStart-Methode in eine GUI-Klasse und dort ist dann automatisch ein passendes Control verfügbar. Events sind in diesem Fall gar nicht erforderlich.

Für WPF gilt alles vollkommen analog, nur dass Dispatcher.Invoke statt Control.Invoke verwendet wird.

 

Hallo gfoidl,

Zitat:
Ich finde es passender wenn der Aufrufer nichts zusätzliches implementieren muss - für mich bedeutet das auch Kapselung.

nicht jeder Aufrufer muss ja was zusätzliches implementieren, sondern nur Aufrufer, die besondere Anforderungen haben.

Zitat:
Dem Aufrufer kann es ja egal sein ob die Methode die er ausführen lässt im gleichen Thread läuft oder in einem anderen.

Dem GUI ist das aber - im hier besprochenen Fall - eben nicht egal. Es ist eben eine Anforderung, die das GUI stellt. Und genau deshalb sollte es auch in der Zuständigkeit des GUIs liegen, dafür zu sorgen, dass alle GUI-Zugriffe aus dem GUI-Thread erfolgen. Deine Argumentation umgedreht: Es kann und sollte der aufgerufenen Klasse doch egal sein, dass der Aufrufer besondere Anforderungen hat. Ansonsten müsstest du ja die Klasse ändern, wenn ein weiterer Aufrufer kommt, der noch andere Anforderungen hat.

Zitat:
Er sollte gar nicht mitbekommen was in der Methode passiert - hauptsache es geschieht das was passieren soll.

Richtig!

Zitat:
Der Aufwand einen SynchronizationContext oder eine AsyncOperation zu implementieren ist (für mich) geringer als für jeden EventHandler ein Invoke zu schreiben.

Aus Bequemlichkeitsgründen kann man das auch machen. Das habe ich ja gesagt. Und da SynchronizationContext die Spezialitäten der typischen Arten von Aufrufern abhandelt, ist das auch nicht wirklich verwerflich, wenn man es benutzt, aber richtig sauber ist es eben auch nicht.

Stell dir vor, du hast einen Worker-Thread, der seine Arbeitsaufträge über eine synchronisierte Queue bekommt ( SyncQueue <T> - Eine praktische Job-Queue). Und stell dir weiter vor, dieser Thread benutzt ein (COM-)Objekt, auf das alle Zugriffe von dem Thread erfolgen müssen, der das Objekt erzeugt hat. Dann müsste nach deiner Logik die aufgerufene Klasse die EventHandler, die solche Zugriffe ausführen, an diesen Thread delegieren. Wie man Zugriffe an diesen Thread delegiert, weiß aber die SynchronizationContext-Klasse nicht und schon funktioniert die heile und bequeme Welt nicht mehr. Spätestens dann muss das Delegieren doch vom EventHandler vorgenommen werden(*), der ja weiß wie das geht. Und damit zeigt sich, es liegt in der Zuständigkeit des Aufrufers dafür zu sorgen, dass seine speziellen Anforderungen berücksichtigt werden.

Überhaupt kann man die Ausführung nur an Threads delegieren, die kooperativ sind, also extra so programmiert sind, dass man ihnen Arbeitsaufträge übergeben kann. Wenn ein Thread von Anfang bis Ende eine sequentielle Verarbeitung durchführt, dann tut er eben stur genau das und nicht das, was andere Threads gerne von ihm hätten. So kann man z.B. an den Main-Thread eines Konsolenprogramms üblicherweise nichts delegieren. Spätestens hier scheitert also selbst die SynchronizationContext-Klasse.

Zurück zum Ausgangsfall mit dem GUI: Wenn andersherum im EventHandler gar keine Zugriffe auf Controls o.ä. erfolgen, sondern einfach nur z.B. geloggt werden soll, dann würde bei deinem Vorschlag trotzdem und dann unnötigerweise an den GUI-Thread delegiert werden. Das zeigt nochmal: Nur der Aufrufer weiß, ob das nötig ist. Also soll auch der Aufrufer und nicht die aufgerufene Klasse entscheiden.

Zitat:
... als für jeden EventHandler ein Invoke zu schreiben

Appropos mehrere EventHandler. Jeder Aufrufer kann seine eigenen Anforderungen haben. Man würde also gar nicht mit mit einem SynchronizationContext auskommen, sondern bräuchte für jeden registrieren EventHandler einen eigenen. Das wäre zwar nicht vollkommen undenkbar, würde aber zumindest die Komplexität der Klasse weiter unnötig steigern. Da ist es doch wesentlich einfacher, wenn der jeweilige EventHandler das jeweils nötige veranlasst.

Ein weiterer Punkt ist, dass gerade Control.Invoke eine relativ teure Operation ist (und auch SynchronizationContext.Send macht im Fall von Windows Forms intern auch nichts anders als Control.Invoke). Wird das Event (möglicherweise) häufig ausgeführt, kann es durchaus zu Problemen kommen (selbst bei Control.BeginInvoke/SynchronizationContext.Post). Ein EventHandler kann dagegen durchaus erstmal mehrere Anforderungen sammeln und für sie zusammen in einem Rutsch Control.Invoke aufrufen. Es ist eh unsinnig das GUI mehr als ca. 10 mal pro Sekunde zu aktualisieren. Wenn der Event das Delegieren übernehmen würde, handelt man sich also möglicherweise echte Probleme ein und ist gleichzeitig weniger flexibel.

Nicht vergessen darf man auch, dass der eigentliche Zweck von Events Entkoppelung ist. Schon alleine deshalb wäre es also geradezu widersinnig, wenn ein Event versuchen würde, spezielle Anforderungen seiner Abonnenten zu berücksichtigen.

Zitat:
Oder sehe ich da etwas falsch?

Zumindest sehe ich das klar anders. :-) Ich hoffe, ich konnte dich überzeugen.

herbivore

(*) Genaugenommen muss man natürlich nicht die aufgerufene Klasse ändern. Es würde reichen, wenn der Aufrufer der Klasse einen eigenen, passenden SynchronizationContext mitgibt, der weiß, wie man korrekt delegieren muss und kann. Das wäre eine saubere Alternative. Aber eben auch nur, weil es dann wieder in der Zuständigkeit des Aufrufers liegt zu bestimmen, was seine besonderen Anforderungen sind und wie diese zu erfüllen sind.

Suchhilfe: 1000 Worte, Event, Events, EventHandler, zuständig, zuständige, zuständiger, Zuständigkeit, Zuständigkeiten, verantwortlich, verantwortliche, verantwortlicher, Verantwortlichkeit, Verantwortlichkeiten
25.06.2009 09:20 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
gfoidl gfoidl ist männlich
myCSharp.de-Team

avatar-2894.jpg


Dabei seit: 07.06.2009
Beiträge: 6.659
Entwicklungsumgebung: VS 2019
Herkunft: Waidring


gfoidl ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat:
...nur Aufrufer, die besondere Anforderungen haben....Es kann und sollte der aufgerufenen Klasse doch egal sein, dass der Aufrufer besondere Anforderungen hat....es liegt in der Zuständigkeit des Aufrufers dafür zu sorgen, dass seine speziellen Anforderungen berücksichtigt werden....Nur der Aufrufer weiß, ob das nötig ist. Also soll auch der Aufrufer und nicht die aufgerufene Klasse entscheiden.

Mit diesen Argumenten hast du mich überzeugt. Danke für die gute Erklärung.

Wie stehst du dann zum BackgroundWorker?
Dieser funktioniert ja genau nach dem Prinzip des SynchronizationContext. Ist es bei diesem OK da sein Aufgabenbereich auf Arbeiten im Thread und Events an GUI eingeschränkt ist?


mfG Gü
25.06.2009 10:45 Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Poweruser/ Experte

avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.478
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo gfoidl,

der BackgroundWorker ist aus meiner Sicht eine reine Komfortklasse, die einen Thread als Component kapselt, damit man ihn direkt im Designer verwenden kann. Er erzielt darauf ab, es dem Aufrufer so einfach wie möglich zu machen. Er ist also genau die oben angesprochene Bequemlichkeitslösung. Und da die SynchronizationContext-Klasse für die gängigen Anwendungs- bzw. Thread-Typen funktioniert, ist das praktisch gesehen auch ok.

herbivore
25.06.2009 10:54 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
gfoidl gfoidl ist männlich
myCSharp.de-Team

avatar-2894.jpg


Dabei seit: 07.06.2009
Beiträge: 6.659
Entwicklungsumgebung: VS 2019
Herkunft: Waidring


gfoidl ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Danke. Daumen hoch

Jetzt sehe ich das auch so wie du.

mfG Gü
25.06.2009 11:02 Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 11 Jahre.
Der letzte Beitrag ist älter als 11 Jahre.
Antwort erstellen


© Copyright 2003-2020 myCSharp.de-Team | Impressum | Datenschutz | Alle Rechte vorbehalten. | Dieses Portal verwendet zum korrekten Betrieb Cookies. 12.08.2020 20:21