Laden...

Wann wird Control.BeginInvoke durch Delegate aufgerufen?

Erstellt von spoochie vor 7 Jahren Letzter Beitrag vor 7 Jahren 1.905 Views
S
spoochie Themenstarter:in
155 Beiträge seit 2007
vor 7 Jahren
Wann wird Control.BeginInvoke durch Delegate aufgerufen?

Hallo,

ich habe eine Sache, die ich mir nicht erklären kann. Ich habe zwei Funktionen, die aus einem anderen Thread auf eine Pictuebox zugreifen.

GetLagerEbene0
GetLagerEbene2

Diese Funktionen rufen sich selber auf, wenn ein Invoke von Nöten ist.

Ich habe eine Tracefunktion, die mir sagt wann und wie (Invoke / kein Invoke) die Funktionen aufgerufen werden

Mein Trace gibt mir folgendes aus

10.08.2016 09:26:01 838 GetLagerEbene0 INVOKE
10.08.2016 09:26:05 846 GetLagerEbene2 INVOKE
10.08.2016 09:26:05 846 GetLagerEbene0
10.08.2016 09:26:05 866 GetLagerEbene2

Zwischen dem Aufruf von GetLagerEbene0 und GetLagerEbene2 ist ein Thread.Sleep(4000)

Ich hätte jetzt erwartet, das der Trace so aussieht. 🤔

GetLagerEbene0 INVOKE
GetLagerEbene0
GetLagerEbene2 INVOKE
GetLagerEbene2

Wann wird das INvoke mit dem Delegaten ausgefuehrt?

MfG spoochie

16.842 Beiträge seit 2008
vor 7 Jahren

Davon abgesehen, dass man auf UI Elemente nur aus dem Mainthread zugreifen soll / darf; zeig mal bitte Deinen Code.
So ist es nur raten, was genau der Code wirklich macht.

W
872 Beiträge seit 2005
vor 7 Jahren

Du solltest in Deinem Programm beide Reihenfolgen abdecken.
Wenn Du mit Invoke arbeitest, dann ist keine Reihenfolge garantiert.
Willst Du eine Reihenfolge herstellen, dann musst Du mittels lock synchronisieren.

S
spoochie Themenstarter:in
155 Beiträge seit 2007
vor 7 Jahren

Hallo Abt,

ich habe eine Socketverbindung, die in einem eigenen Thread auf Daten wartet und aboniere im main ein Event, der die Daten entgegen nimmt. Wie kann ich denn da vom Mainthread ohne Invoke zugreifen?


private void komm_OnKommdata(object sender, KommdataArgs e)
{
      if (bLagerChanged)
                            {
                                if (Ebene == 0)
                                {
                                    ShowFrame(Ebene);
                                    GetLagerEbene0(0, 0);
                                }
                            }
                            //Pruefen ob zum TRansport ausgewaehltes Coil im aktuellen Lagerbereich liegt
                            bool bgefunden = false;
                            string sLagerbereich = "?";
                            string sLagerOrt = "?";
                            string sLagerOrtneu = "?";
                            Thread.Sleep(4000);
                            foreach (Ring rg in liTransport)
                            {
                                double dDurchmesser, dBreite;
                                sLagerOrt = GetRingLagerPos(rg, out dBreite, out dDurchmesser, out   sLagerbereich, out sLagerOrtneu);
                                if (sLagerbereich.Trim() == sCurrStoreName.Trim())
                                {
                                    bgefunden = true;
                                    break;
                                }
                            }
                            if (bgefunden == true)
                            {
                                Ebene = 2;
                                ShowFrame(iAktEbene);
                                GetLagerEbene2(0, 0, false, "x " + sLagerOrtneu);
}

Hmm trotz des Thread.Sleeps wartet er auf die zweite Funktion?

@ weismat: Wie würde ich das denn mit lock synchronisieren? Wenn ich in beiden Funktionen ein lock mache ist doch noch immer nicht gewährleistet, was zuerst abgearbeitet wird, da er die Funktion ja sozusagen verlässt, wenn ein Invoke gemacht werden muss.

MfG spoochie

6.911 Beiträge seit 2009
vor 7 Jahren

Hallo spoochie,

[FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke) hast du sicher schon gelesen, aber vllt. ist dir dort der springende Punkt deiner Frage nicht aufgefallen.

Wenn es eh schon der UI-Thread ist, so ist kein Control.Invoke bzw. zurückdelegieren in den UI-Kontext nötig. Ein Thread.Sleep lässt den aktuellen Thread nur die genannte Zeit warten -- d.h. nicht dass ein Thread-Kontextwechsel stattfindet. Wenn also der Main-Thread (der UI-Thread) per Thread.Sleep angehalten wird, so ist auch kein Zurückdelegieren nötig.

Anders kann es bei Task.Delay sein, v.a. dann wenn ConfiguraAwaitable(false) verwendet wird.

In deinem gezeigten Code sieht man nicht wo du das Control.BeginInvoke aufrufst -- hast du dort mit dem Debugger schon geschaut wie und wann die Haltepunkte erreicht werden ([Artikel] Debugger: Wie verwende ich den von Visual Studio?).

mfG Gü

PS: Dein Thread.Sleep hast du wohl hoffentlich nicht im Live-Code -> [FAQ] Warum blockiert mein GUI?

Hallo weismat,

Wenn Du mit Invoke arbeitest, dann ist keine Reihenfolge garantiert.

Doch, es ist die Reihenfolge in welcher die Aktionen in die Queue gestellt werden.
WinForms arbeitet diese dann einfach ab.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

S
spoochie Themenstarter:in
155 Beiträge seit 2007
vor 7 Jahren

Hallo gfoidl,

ich habe bis Dato gedacht, das ein Event dem Thread zugrunde liegt, der den Event feuert.

Ein Invoke scheint ja nötig zu sein, weil er mir ja sagt, das ein Invoke nötig ist. Das Thread.Sleep habe ich nur temporär zum Testen eingebaut, weil ich sicher gehen wollte, dass der Invoke genug Zeit zum ausfuehren bekommt. Aber trotzdem wird der Delegate erst später ausgefuehrt.

MfG spoochie

6.911 Beiträge seit 2009
vor 7 Jahren

Hallo spoochie,

ein Event dem Thread zugrunde liegt, der den Event feuert.

Dem ist auch so. Wenn ein Event in Thread-K gefeuert wird, so läuft der/die Event-Handler auch in Thread-K -> daher ist auch ein Zurückdelegieren in den UI-Thread nötig.

Deinen Antwort hat sich jetzt mit meine Edit zum Debugger überschnitten. Hast du das mit dem Debugger und Haltepunkten schon geprüft?

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

S
spoochie Themenstarter:in
155 Beiträge seit 2007
vor 7 Jahren

Hallo gfoidl,

mit dem debugger habe ich mir das ganze schon angesehen. GetLagerEbene0 geht sofort zurück und deswegen habe ich das Sleep zum testen eingebaut, um dem Invoke die nötige Zeit zu geben. Trotzdem wird der Invoke nicht ausgefuehrt. Erst wenn GetLagerEbene2 das erste mal ausgefuehrt wird.


 private void GetLagerEbene2(int iX, int iY, bool bScroll, string sScrollRichtung)
        {
            lock (oLockdictZugriff)
            {
                if (this.InvokeRequired)
                {
                    Komm.Writelog("test", "GetLagerEbene2 INVOKE");
                    BeginInvoke(new MethodInvoker(delegate() { GetLagerEbene2(iX, iY, bScroll,  sScrollRichtung); }));
                }
                else
                {
                    //Code ausfuehren
                }
            }
       }

Für GetLagerEbene2 ist der Code genauso aufgebaut

Ich finde es merkwürdig.

MfG spoochie

6.911 Beiträge seit 2009
vor 7 Jahren

Hallo spoochie,

für was ist das lock (oLockdictZugriff) gut? Nicht dass du dir hiermit das falsch synchronisiert -- den Zusammenhang kann ich aus den Snippets nicht erkennen.

Hier ist eigentlich ncihts zu Synchronisieren, da die Nachrichten vom Invoke eh in eine Nachrichtenschlange gestellt werden (Producer/Consuer) und WinForms dann eine Nachricht nach der anderen abarbeitet.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

S
spoochie Themenstarter:in
155 Beiträge seit 2007
vor 7 Jahren

Hallo gfoidl,

sry, dass

lock

ist für den Synchronisierten Zufriff auf ein Dictionary. Wenn das so mit der Nachrichtenschlange ist, dann ist das Verhalten doch noch seltsamer, weil Die Funktion ja sofort wieder zurückgeht und sich selbst aufruft, wenn ein Invoke nötig ist. Zwischen den Aufrufen liegen immerhin 4 Sekunden.

MfG spoochie

16.842 Beiträge seit 2008
vor 7 Jahren

Der lock() sollte so nah wie möglich an der entsprechenden Stelle stehen, wenn er notwendig ist.
Du lockst über den gesamten Inhalt der Methode - auch, wenn Du das gar nicht brauchst, weil Du invokest.
Seiteneffekte dadurch potentiell möglich!

Trenne UI und vor allem dessen Synchronisationen immer von Business-Logik!!

6.911 Beiträge seit 2009
vor 7 Jahren

Hallo spoochie,

seltsam finde ich nur das lock 😉

Was passiert hier:1.GetLagerEbene2 wird aufgerufen 1.der Monitor (lock) wird erreicht -- wenn dieser noch nicht "gehalten" wird, so gehts weiter, sonst wird gewartet bis dieser wieder "frei" ist 1.es wird geprüft ob eine Delegieren in den UI-Kontext nötig ist 1.falls ja: per BeginInvoke wird die Nachricht (GetLagerEbene2) in die Nachrichtenschlange gestellt und ohne Warten auf dessen Ausführung wieder zurückgekehrt. Würdest du hier nur Invoke verwenden, so sollte ein Deadlock passieren (aufgrund vom lock)
falls nein: der Code wird ausgeführt

1.GetLagerEbene2 wird (irgendwann, wenn es WinForms anhand der Nachrichtenschleife für richtig hält) ausgeführt 1.der Monitor (lock) wird erreicht -- dieser ist gehalten, daher wird gewartet...

Nimm dir Abts Rat zu Herzen und setze den lock ev. um // Code ausführen, aber nicht um das Gesamte.

Schau dir das im Debugger auch mit der Thread-Ansicht an, dann siehst du wann welcher Thread blockiert ist.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

W
872 Beiträge seit 2005
vor 7 Jahren

Innerhalb des Invoke-Teils solltest Du wenn möglich nicht auf das Dictionary zugreifen.
Du könntest z.B. vor dem Invoke Deinen Wert in eine lokale Variable schreiben, so daß Du dann im GUI Thread nicht mehr das Dictionary schützen musst.
Bei der Reihenfolge ist schon klar, daß Invoke0 immer vor Invoke2 kommt - aber ob etwas dazwischen passiert oder nicht, dass ist nicht klar - so hatte ich das gemeint.

S
spoochie Themenstarter:in
155 Beiträge seit 2007
vor 7 Jahren

Hallo zusammen,

schande, es lag tatsächlich an dem lock in den Funktionen, damit habe ich mir die weitere Verarbeitung blockiert.

Ich danke euch, dass ihr mich auf den richtigen Weg geführt habt.

@weismat, wenn ich die Werte in eine lokale Variable schreiben würde, würde ich dann nicht trotzdem Probleme bekommen, weil es nur eine Referenz wäre?

Lieben Gruß spoochie

W
872 Beiträge seit 2005
vor 7 Jahren

Ich habe bewusst von Werten und nicht Objekten gesprochen - Werttypen.

S
spoochie Themenstarter:in
155 Beiträge seit 2007
vor 7 Jahren

OKay danke 😉

Gruß spoochie

6.911 Beiträge seit 2009
vor 7 Jahren

Hallo,

dazu habe ich oben erwähnt dass es sich um einen Producer/Consumer handelt. Lass das einmal sacken und schau dir dazu auch SyncQueue <T> - Eine praktische Job-Queue an. Ähnliches passiert beim Control.(Begin)Invoke in der Nachrichtenschleife von WinForms.

Die Aussage von weismat verallgemeinert auf "immutable Types" passt auch.

Jetzt müssen wir dann aber aufpassen dass das Thema nicht zu sehr abweicht -- ggf. einen neuen Thread dazu erstellen.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"