Ungültiger threadübergreifender Vorgang - trotz Invoke!

  • C#
  • .NET (FX) 1.0–2.0

Es gibt 12 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Ungültiger threadübergreifender Vorgang - trotz Invoke!

    Guten morgen liebe Community,

    ich beschäftige mich nun schon seit einiger Zeit mit einem Multi-Threading-Projekt.
    Im Prinzip könnte man die Architektur folgendermaßen herunterbrechen:
    • MainThread => Arbeitsthread
    • GuiThread => Zeigt die Benutzeroberfläche
    • Singleton => Wird vom MainThread erstellt und beherbergt den GuiThread und die öffentlich zugängliche Variable der Benutzeroberfläche (Form), ui.

    Der GuiThread wird vom MainThread erzeugt.

    Möchte ich jetzt z.B. ein Ergebnis einer Rechnung, welche im MainThread gemacht wurde, im GuiThread anzeigen, mache ich das immer wie folgt:

    C#-Quellcode

    1. //ich befinde mich im Arbeitsthread
    2. MethodInvoker threadSafeCall = delegate {
    3. singleton.ui.lblResult.text = "123";
    4. };
    5. if (singleton.ui.lblResult.InvokeRequired)
    6. singleton.ui.lblResult.BeginInvoke(threadSafeCall);
    7. else
    8. threadSafeCall();


    Ich weiß nun nicht genau, in welcher Konstellation genau, aber es kommt dennoch vor, dass ich eine InvalidOperationException bekomme mit der Beschreibung "Ungültiger threadübergreifender Vorgang".
    Nun wirft das ein Paar Fragen bei mir auf:
    • Besteht trotz Invoke die Möglichkeit, diese Exception zu bekommen?
    • Geht es, um z.B. ein Label zu aktualisieren, wenn man das Invoke auf die Form anwendet, die das Label beherbergt?
    • Besteht die Möglichkeit herauszufinden, von welchem Thread aus eine Funktion aufgerufen wurde?

    Danke für Eure Mithilfe!

    ~LG Marvin

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „TRiViUM“ ()

    TRiViUM schrieb:

    Besteht trotz Invoke die Möglichkeit, diese Exception zu bekommen?
    Wenn Du falsch invokst - Ja.
    Poste mal den betreffenden Invoke-Code.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @RodFromGermany
    Von Dir hatte ich bereits in der Vergangenheit die Info, dass man anstelle vom MethodInvoker jetzt Action benutzt.
    Hab es aber erstmal so gelassen, weil ich nicht noch mehr Baustellen haben wollte.

    C#-Quellcode

    1. public string F_START_FILLING_AND_WAIT_FOR_WATER_LEVEL(string Par1, string Par2, ref string ResComment)
    2. {
    3. try
    4. {
    5. using (var dialog = new frmWait(Par1, 0))
    6. {
    7. //prepare progress bar
    8. dialog.lb_Info.Text = Par1;
    9. dialog.progressBar1.Style = ProgressBarStyle.Marquee;
    10. dialog.progressBar1.MarqueeAnimationSpeed = 50;
    11. dialog.StartPosition = FormStartPosition.CenterScreen;
    12. if (global.ProcessManager.mainUi != null && global.ProcessManager.mainUi.Visible)
    13. {
    14. MethodInvoker threadSaveCall = delegate {
    15. dialog.Show(global.ProcessManager.mainUi);
    16. };
    17. if (global.ProcessManager.mainUi.InvokeRequired)
    18. global.ProcessManager.mainUi.BeginInvoke(threadSaveCall);
    19. else
    20. threadSaveCall();
    21. } else
    22. {
    23. dialog.Show();
    24. dialog.BringToFront();
    25. dialog.Activate();
    26. }
    27. #region value request
    28. var waterStatus = global.ProcessManager.GetWaterStatus();
    29. bool ready = false;
    30. if (Par2.ToLower() == "prefill") ready = waterStatus.HasFlag(ProcessManager.WaterStatus.PreFilled);
    31. else if (Par2.ToLower() == "fill") ready = waterStatus.HasFlag(ProcessManager.WaterStatus.Full);
    32. if (ready)
    33. {
    34. if (dialog.Visible)
    35. {
    36. MethodInvoker threadSafeCall = delegate {
    37. dialog.Close();
    38. };
    39. if (dialog.InvokeRequired)
    40. dialog.BeginInvoke(threadSafeCall);
    41. else
    42. threadSafeCall();
    43. }
    44. return "OK";
    45. }
    46. #endregion
    47. //wait for next cycle
    48. Thread.Sleep(1000);
    49. }
    50. } catch (Exception ex)
    51. {
    52. ResComment = ex.Message + "\r\n";
    53. ResComment += global.ErrorText;
    54. return "NOK";
    55. }
    56. }


    Mir ist gerade etwas aufgefallen.
    Ich rufe, um die frmWait vor der Benutzeroberfläche anzuzeigen, diese auf und übergebe als Owner "mainUi".
    Das mache ich, um die frmWait modal vor mainUi anzuzeigen.
    Und das rufe ich auch schon per Invoke auf.

    Bzw. mit BeginInvoke...fehlt da noch ein EndInvoke ?

    Kann es damit zusammenhängen?

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „TRiViUM“ ()

    @TRiViUM Wie es aussieht, wird der Dialog in irgend einem Thread erstellt.
    Ich würde mal behaupten, dass, wenn Du auf if (dialog.InvokeRequired) einen Haltepunkt setzt, er in den else-Zweig geht.
    Probier das bitte mal aus.
    ====
    EndInvoke: Nein.
    BeginInvoke im Unterschied zu Invoke wartet so lange, bis die App in den Idle-Modus wechselt, dort werden dann alle mit BeginInvoke aufgelaufenen Aktionen ausgeführt.
    Invoke führt die betreffenden Aktionen unmittelbar aus.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    RodFromGermany schrieb:

    in irgend einem Thread

    Der Code ist Teil vom MainThread.

    RodFromGermany schrieb:

    Probier das bitte mal aus

    Du hast falsch gelegen: dialog.InvokeRequired ist true...
    Was ich schon komisch finde, da der Dialog da erstellt wird, wo er auch geschlossen werden soll...

    Danke für die kurze Erklärung nebenbei zu BeginInvoke vs. Invoke :)


    Kann es sein, dass nicht das dialog.Close() am Ende das Problem verursacht, sondern z.B. auch schon das dialog.Visible?

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „TRiViUM“ ()

    TRiViUM schrieb:

    Der Code ist Teil vom MainThread.
    OK, das ist mir auch nicht ganz verständlich, da Du in dieser Prozedur den Dialog erstellst und InvokeRequired abtestest. :/
    Funktioniert es nun?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    RodFromGermany schrieb:

    Funktioniert es nun?


    Ein klares JEIN.
    Ich hab es so leider nicht zum Laufen bekommen.
    Aber scheinbar hat es was damit zu tun, dass ich beim Aufruf dialog.Show als Owner eine Form eines anderen Threads übergebe.

    Ich hab jetzt den Owner weggelassen und rufe nach dialog.Show() jetzt nur noch dialog.BringToFront() auf, damit es im Vordergrund landet. Ganz ohne BeginInvoke - und siehe da, das Flag dialog.InvokeRequired dann ist auch false...

    Dennoch würde ich gern wissen, wie so ein Problem gelöst werden kann.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „TRiViUM“ ()

    TRiViUM schrieb:

    Aber scheinbar hat es was damit zu tun, dass ich beim Aufruf dialog.Show als Owner eine Form eines anderen Threads übergebe.
    Jo - da täte ich erwarten, dass das nicht funktioniert.

    RodFromGermany schrieb:

    BeginInvoke im Unterschied zu Invoke wartet so lange, bis die App in den Idle-Modus wechselt, dort werden dann alle mit BeginInvoke aufgelaufenen Aktionen ausgeführt.
    Kann man das iwo nachlesen?
    Ich hab immer gedacht, der Unterschied sei ein ganz anderer: Bei Invoke wartet der NebenThread, bis der MainThread fertig ist. Das bremst ihn ganz unnötigerweise aus, denn der Nebenthreads will ja gar keine Ergebnisse des MainThreads verarbeiten.

    ErfinderDesRades schrieb:

    Kann man das iwo nachlesen?
    Eher experimentell verifiziert.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Moment, nicht dass hier was falsche gesagt wird. Rod hat das vermutlich nur verdreht beim schreiben.
    Invoke reiht die Funktion in eine Liste der Invokes für den Thread ein und wartet auf die Abarbeitung.
    BeginInvoke reiht die Funktion in eine Liste der Invokes für den Thread ein und ist dann Fertig. Wartet also nicht auf die Abarbeitung.

    Bluespide schrieb:

    Rod hat das vermutlich nur verdreht beim schreiben.
    Teste das bitte selbst.
    Untersuchungen zu Invoke() und BeginInvoke() - Reihenfolge der Abarbeitung
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Der Test zeigt - wie zu erwarten - dass Invoke den aufrufenden Thread blockiert, während BeginInvoke das nicht tut.
    Vermutlich beide fügen die Aufrufe derselben MesssageQueue zu, die kontinuirlich abgearbeitet wird.
    Die direkten List.Add-Aufrufe sind halt direkt, und nicht in der MessageQueue.
    Da bei BeginInvoke nicht gewartet wird, bis die MesssageQueue so weit ist, den Aufruf umzusetzen, erfolgen die direkten Aufrufe viel schneller - und am Ende wird Richtextbox.Addlines ausgeführt, noch bevor die MesssageQueue die BeginInvoketen Aufrufe abgearbeitet hat.
    Danach arbeitet die MessageQueue die Aufrufe ab, und fügt die Zahlen in die Liste - aber die werden von der Richtextbox dann ja nicht mehr berücksichtigt.

    Das bedeutet aber doch nicht, dass BeginInvokete Aufrufe erst bei Application.Idle ausgeführt werden.
    Sondern die werden ganz genauso in die M-Queue gestellt wie bei Invoke.
    Nur BeginInvoke wartet nicht ab, bis der Aufruf in der M-Queue dann dran ist.

    Wie gesagt: Invoke blockiert den aufrufenden Thread. Da in diesem Test Aufrufer und aufgerufener derselbe ist, blockiert er sich also selbst - was am Ende dann schön aussieht, weil alle Zahlen kommen in der "richtigen" Reihenfolge.
    Aber genau das - die "richtige" Reihenfolge ist bei Threading ja unerwünscht. Jeder Thread soll seine Arbeit machen, und die Reihenfolge der Ergebnisse ist undefiniert.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()