mal wieder Thread-Problem (Ungültiger threadübergreifender Vorgang)

  • VB.NET
  • .NET (FX) 4.5–4.8

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

    mal wieder Thread-Problem (Ungültiger threadübergreifender Vorgang)

    Hallo zusammen,

    ich habe ein Problem mit dem Thread-Handling.
    Bei mir ist es so, dass ich Hardware über eine serielle Schnittstelle angeschlossen habe und wenn von dort aus ein Befehl kommt, soll sich etwas auf meiner Oberfläche ändern. Hab ich schon hunderttausendmal gemacht, aber dieses Mal läuft's schief und ich weiß nicht warum.

    Das ist die Delegate-Routine über die ich meine Zuweisung mache...

    Quellcode

    1. Private Delegate Sub ParentDelegate(ByVal Parent As Control, ByVal Child As Control)
    2. Private Sub DelegateToParent(ByVal Parent As Control, ByVal Child As Control)
    3. If Child.InvokeRequired Then
    4. Dim D As New ParentDelegate(AddressOf SetToParent)
    5. Dim PArray() As Object = New Object() {Parent, Child}
    6. Child.Invoke(D, PArray)
    7. Else
    8. Child.Parent = Parent
    9. End If
    10. End Sub


    ... und trotzdem bekomme ich die Fehlermeldung...
    Ungültiger threadübergreifender Vorgang


    ... weil die Property InvokeRequired false ist, obwohl ich durch's debuggung genau sehe, dass der Impuls zum Aufrufen der Routine aus meinem seriellen Schnittstellenkram heraus kommt. Dann läuft er zum Befehl

    Quellcode

    1. Child.Parent = Parent
    und wirft natürlich den Fehler.

    Zur Info:
    das Control Child ist eine Ableitung einer PictureBox
    das Control Parent ist das Panel eines SplitContainer
    Unmittelbar vor dem Aufruf der DelegateToParent wird das Child erst erstellt (Child = new...). Könnte es damit zusammen hängen? Sprich der Handle auf das Objekt ist noch nicht bereit?

    Wäre schön, wenn mir jemand helfen könnte.

    Danke,
    und viele Grüße,
    Alex
    Hey zusammen,

    wie sieht's aus? Hat keiner eine Idee?
    Derzeit arbeite ich an der Stelle mit einem Workaround, den ich gerne eliminieren würde
    1. Statt meine Daten in das Parent-Objekt zu schreiben, schreibe ich in eine globale Variable.
    2. In meinem Parent-Objekt gibt es nun einen Timer, der die Daten aus der Variable pollt.

    Ist nicht so toll. Also wie gesagt, falls jemand eine Idee zu meinem Problem hat, immer raus damit.

    Viele Grüße,
    Alex
    @WilliamSpiderWeb Mit FW 4.5 geht das etwas freundlicher, ohne direktes Delegate. Teste mal dies:

    VB.NET-Quellcode

    1. Public Sub DoIt(param1 As Boolean, param2 As Boolean)
    2. If Me.InvokeRequired Then
    3. ' ruft über BeginInvoke sich selbst auf
    4. Dim action As New Action(Of Boolean, Boolean)(AddressOf Me.DoIt)
    5. Me.BeginInvoke(action, param1, param2)
    6. Return
    7. End If
    8. ' etwas mit param1 und param2 tun
    9. End Sub
    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!

    WilliamSpiderWeb schrieb:

    Zur Info:
    das Control Child ist eine Ableitung einer PictureBox
    das Control Parent ist das Panel eines SplitContainer
    Unmittelbar vor dem Aufruf der DelegateToParent wird das Child erst erstellt (Child = new...). Könnte es damit zusammen hängen? Sprich der Handle auf das Objekt ist noch nicht bereit?
    Das ist problematisch - vermutlich erzeugst du Child im NebenThread, willst es aber in ein Mainthread-Form einfügen - das geht nicht.

    Alle Controls müssen im MainThread erzeugt werden.

    Aber auch sonst ist das Problematisch, einfach, weil man zur Laufzeit möglichst keine Controls erzeugen soll. Denn Controls sind keine Daten - diese Bereiche sollte man möglichst strikt trennen.
    Ich kenne jetzt nicht deinen Anwendungszweck, aber mit großer Wahrscheinlichkeit gibt es Ansätze, die ohne derlei gewurstel auskommen.

    Neu

    Hallo ErfinderDesRades,

    ich weiß, mein ursprünglicher Beitrag ist schon ein paar Jahre alt, allerdings habe ich aktuell wieder ein ähnliches Problem (eigentlich so ziemlich das Gleiche). Sieht so aus als hätte ich da noch einiges nicht verstanden. (Damals habe ich mir wohl mit einem Workaround geholfen, oder das Projekt verworfen. Ich weiß es nicht mehr genau)

    Da ich an Deiner letzten Aussage anknüpfen möchte, schreibe ich eine Antwort statt einen neuen Beitrag zu eröffnen.
    Aber auch sonst ist das Problematisch, einfach, weil man zur Laufzeit möglichst keine Controls erzeugen soll.

    Warum sollte man das nicht tun?

    In meinem aktuellen Projekt gibt es ein normales Hauptfenster und eine Klasse, die die Funktionen eines Mikrocontrollers darstellt, mit dem sie per RS232 kommuniziert. Das heißt, die Antworten des Controllers laufen in einem anderen Thread als das Hauptfenster. Zur Darstellung meiner Daten habe ich mir eigene Controls gebaut. Zum Beispiel ein Control abgeleitet vom Panel und innerhalb dieses Panels gibt es eine Liste von Labels. Da ich aber erst zur Laufzeit weiß, wieviele Labels auf das Panel gehören, habe ich in meinem Control eine List(of Label) erstellt. Genau das Control lässt sich dann logischerweise nicht im Designer direkt auf das Hauptfenster setzen, sondern kann erst zur Laufzeit erstellt werden. Allerdings passiert das Erstellen als Reaktion auf ein Event meiner Mikrocontroller-Klasse.

    Da beißt sich dann die Katze in den Schwanz, weil hier das Invoke wieder nicht funktioniert.
    Wie wäre hier das richtige Vorgehen? Ich möchte eigene Controls bauen, deren Inhalt ich erst zur Laufzeit kenne, muss diese aber als Reaktion auf den Mikrocontroller befüllen können.

    Danke schon einmal im Voraus für hilfreiche Tipps.

    Viele Grüße,
    Alexander

    Neu

    Daten kommen nebenläufig rein (wahrscheinlich über den DataReceived-EventHandler). Dann informiert man den Hauptthread, dass Daten gekommen sind, indem man z.B. mit Invoke eine andere Methode aufruft. In dieser anderen Methode werden die Daten in ein Control gepackt oder Controls erstellt - falls das nicht anders möglich ist. Was können Deine dynamischen Controls, was ein statisches Control nicht kann? In welchen Zeitabständen sollen neue Controls generiert werden?
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    Neu

    Hallo VaporiZed,

    danke für Dein Interesse an meinem, hoffentlich nicht hoffnungslosen Fall. ;)
    Deine Zusammenfassung ist erstmal richtig. Ich bin mir auch bewusst, dass ich in der Sub, die auf das DataReceived-Event reagiert, die Eigenschaften der Controls (egal ob eigene oder die Standard-Controls) über Invoke oder BeginInvoke füllen kann.

    Ein Teil meiner Controls ist abgeleitet vom Label, kann also erstmal alles, was das Label kann und ist lediglich mit ein paar graphischen Funktionien erweitert (Netter Rahmen drum, Schriftgröße wird relativ zur Size-Eigenschaft automatisch berechnen, und so).

    Der andere Teil der Controls ist abgeleitet vom Panel. Auf dem Panel sind dann MyLabel (wie oben beschrieben), PictureBox, MyButton(so ähnlich wie MyLabel), eine eigene ProgressBar, u.s.w. angeordnet. Das Panel wird dann im Fullscreen auf die Form gelegt und ist dann eine ansehnliche GUI für Fertigungs- und Prüfanlagen... so der Plan :)

    Inzwischen habe ich im Laufe des Tages schon einmal "herausgefunden", dass sobald ich das Property "Buttons" (s.u.) als ReadOnly definiere, das Control "ListScreen" im Designer auf meine Form gesetzt werden kann. Dann funktionieren auch die Invoke-Funktionen. Zuvor, als ich die Objekte von zum Beispiel "ListScreen" zur Laufzeit auf die Form gesetzt habe, sind die Invoke-Funktionien gescheitert.

    VB.NET-Quellcode

    1. Public Class ListScreen
    2. Inherits ClearScreen
    3. Protected m_Buttons As New List(Of MyButton)
    4. Public ReadOnly Property Buttons As List(Of MyButton)
    5. Get
    6. Return m_Buttons
    7. End Get
    8. End Property
    9. [...]
    10. End Class


    wobei wie gesagt

    VB.NET-Quellcode

    1. Public Class ClearScreen
    2. Inherits Panel
    3. [...]
    4. End Class
    5. Public Class MyButton
    6. Inherits MyLabel
    7. [...]
    8. End Class
    9. Public Class MyLabel
    10. Inherits Label
    11. [...]
    12. End Class


    Es gibt bestimmt auch andere Möglichkeiten, bestimmt auch bessere, seine GUI nach eigenen Wünschen zu gestalten. Diskutiere ich auch gerne durch. Aber im ersten Anlauf muss ich verstehen, warum der Invoke-Aufruf für mich im einen Fall funktioniert und im anderen Fall nicht.

    Viele Grüße,
    Alex

    EDIT: Code-Brackets in Vbnet-Brackets umgeändert.
    EDIT2: Tippfehler korrigiert

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

    Neu

    tl;dr
    Was genau heißt, dass Invoke nicht funktioniert oder dass die Invoke-Funktion scheitert?
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    Neu

    Das Verändern der Eigenschaftswerten der Controls habe ich durch Aufruf der Sub DelegateSetText gemacht

    VB.NET-Quellcode

    1. Public Class frmMain
    2. Private Delegate Sub DSetText(ByVal C As Control, ByVal Text As String)
    3. Private Sub DelegateSetText(ByVal C As Control, ByVal Text As String)
    4. If C.InvokeRequired Then
    5. Dim D As New DSetText(AddressOf DelegateSetText)
    6. Dim PArray() As Object = New Object() {C, Text}
    7. C.Invoke(D, PArray)
    8. Else
    9. C.Text = Text
    10. End If
    11. End Sub
    12. [...]
    13. End Class


    Da ich den Code heute umgeschrieben habe, kann ich den genauen Wortlaut nicht mehr reproduzieren, allerdings ist beim Ausführen der Zeile "C.Text = Text" der typische Fehler zum threadübergreifendem Zugriff geworfen worden.

    Neu

    Das sind jetzt aber 2 Paar Wintersocken.
    Das Invoke beim Delegate heißt: Delegate aufrufen/ausführen.
    Was Du aber machen musst, ist mithilfe von !DeinForm.Invoke! etwas im Hauptthread machen lassen, und zwar die Controls ändern.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    Neu

    Ich kannte das bisher nur so. Das wurde mir vor einigen Jahren mal so erklärt, klang für mich einleuchtend und ich hab das seit dem so übernommen. Wie gesagt, ich kann nicht behaupten, das Thema vollumfänglich verstanden zu haben. Da hielt sich die Experimentierfreude in Grenzen.
    Wie sollte man's besser machen?

    Neu

    WilliamSpiderWeb schrieb:

    Wie sollte man's besser machen?

    Das hängt immer von den Umständen ab.

    So ist es zwar das gleiche Ergebniss, aber auch einfacherer Code. Besser ist m.M.n. statt Invoke, BeginInvoke zu nehmen. Mit Invoke wird das sofort ausgeführt, egal was im GUI Thread noch so ansteht, mit BeginInvoke wird das ausgeführt sobald Raum dafür da ist.
    Das kannst du dir etwa so vorstellen: Im Supermarkt wäre Invoke sich an der Kasse vordrängeln, BeginInvoke ganz normal hinten anstellen.

    In diesem Beispiel wird statt Control.InvokeRequired und Control.Invoke/Control.BeginInvoke, halt Form.InvokeRequired und Form.BeginInvoke verwendet. Was wie gesagt aufs gleiche hinausläuft, weil Form von Control erbt.

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private thread As Thread
    3. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    4. thread = New Thread(AddressOf ThreadWork)
    5. thread.Start()
    6. End Sub
    7. Private Sub ThreadWork()
    8. 'damit invoked werden muss, hier ein nutzloser Thread aus dem die Funktion gecallt wird
    9. ChangeText(Label1, "TEXT")
    10. End Sub
    11. Private Sub ChangeText(target As Control, text As String)
    12. If InvokeRequired Then
    13. BeginInvoke(Sub() ChangeText(target, text))
    14. Else
    15. target.Text = text
    16. End If
    17. End Sub
    18. End Class

    Neu

    Vereinfahcung:

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private thread As Thread
    3. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    4. thread = New Thread(AddressOf ThreadWork)
    5. thread.Start()
    6. End Sub
    7. Private Sub ThreadWork()
    8. 'damit invoked werden muss, hier ein nutzloser Thread aus dem die Funktion gecallt wird
    9. BeginInvoke(Sub() Label1.Text = "Text")
    10. End Sub
    11. End Class

    Fast immer sind diese selbst-aufrufenden BlaBla-Methoden, die InvokeRequired abfragen, überflüssig.
    Ich jdfs. hab das noch nie gebraucht.
    Ich weiss, wo ich etwas aus einem NebenThread aus aufrufe, und da mussich dann nicht InvokeRequired abfragen, weil das weissichdoch.

    Neu

    MyFault. Habe den Code beim Überfliegen im Kopf durcheinandergehauen. C ist ein Control, kein Delegate. Somit ist Post#10 für'n A…ktenvernichter.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    Neu

    Danke an BitBrösel, schöner Vergleich mit der Kasse. ;)
    Danke an ErfinderDesRades, gebe Dir vollkommen recht, überrascht von einem Nebenthread wurde ich bisher noch nicht.
    Kein Ding VaporiZed.

    Ich würde aber gerne noch einmal auf meine Frage zurückkommen, weswegen ich den Beitrag wieder aufgenommen habe.



    Aber auch sonst ist das Problematisch, einfach, weil man zur Laufzeit möglichst keine Controls erzeugen soll.

    Warum sollte man das nicht tun?

    Neu

    ErfinderDesRades schrieb:

    Denn Controls sind keine Daten - diese Bereiche sollte man möglichst strikt trennen.
    Ich kenne jetzt nicht deinen Anwendungszweck, aber mit großer Wahrscheinlichkeit gibt es Ansätze, die ohne derlei gewurstel auskommen.
    Also sag, was du tun willst, und man kann einen Weg aufzeigen, der ohne dynamische Controls auskommt.

    Ich kann auch abstrakt mal zwei Drawbacks nennen:
    • Databinding wird sehr aufwändig
    • Man muss viel Aufwand treiben, Daten + Controls zu synchronisieren: also die ctls hinmachen, wenn Daten kommen, wegmachen, wenn Daten gelöscht werden, aufrücken, wenn Lücken entstehen, Positionieren überhaupt, undundund...

    Aber wie gesagt: ich weiss nicht, wieviel du vom gesagten verstehst, daher ist am konkreten Beispiel viel besser argumentiert.