Ungültiger threadübergreifender Vorgang (Thread ohne Backgroundworker)

  • VB.NET

Es gibt 16 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    Ungültiger threadübergreifender Vorgang (Thread ohne Backgroundworker)

    Hallo *,

    ich hoffe ihr könnt mir hier bei meinem Problem weiterhelfen.
    Aktuell arbeite ich an einem Verwaltungsrechner, der mehrere Prozesse gleichzeitig abarbeitet. Hierfür verwende ich einen Thread, der permanent in einer do Schleife läuft.


    VB.NET-Quellcode

    1. Public Sub ThreadHauptablaufStarten()
    2. ThreadHauptablauf = New System.Threading.Thread(AddressOf Hauptablauf)
    3. ThreadHauptablauf.IsBackground = True
    4. ThreadHauptablauf.Start()
    5. End Sub
    6. Public Sub Hauptablauf()
    7. Do
    8. HauptThread()
    9. System.Threading.Thread.Sleep(2000)
    10. Loop Until My.Settings.HauptthreadAn = False
    11. ThreadHauptablaufStoppen()
    12. End Sub
    13. Public Sub HauptThread()
    14. If A = True Then
    15. ThreadPruefe1Pruefung = New System.Threading.Thread(AddressOf Pruefe1)
    16. ThreadPruefe1Pruefung.Start()
    17. End If
    18. If B = False Then
    19. End if
    20. '....
    21. End Sub


    Im Hauptthread springe ich nun in einen weiteren Thread, wenn eine IF Bedingung erfüllt ist (Bsp.: ThreadPruefe1Pruefung).
    In diesem Thread wird allerdings eine Kommunikation zu anderen Maschinen benötigt (RS232 und TCPIP Verbindungen), die vorher im ersten Thread geöffnet wurden. Aus diesem Grunde benutze ich die Invoke Methode und führe über eine Case Einweisungen Befehle aus.

    VB.NET-Quellcode

    1. Public Sub Pruefe1
    2. '...
    3. TextBoxM1.Invoke(New BefehlFuerM1(AddressOf BefehleAusThreadNachM1), "Starten")
    4. '...
    5. End Sub
    6. Public Sub BefehleAusThreadNachM1(ByVal Befehl As String)
    7. Select Case Befehl
    8. Case "Starten"
    9. M1TCPIP.Starten()
    10. Case "..."
    11. '....
    12. End Select
    13. End Sub


    Eigentlich funktioniert diese Vorgehensweise ganz gut, nur leider bekomme ich ab und an die bekannte Fehlermeldung "ungültiger threadübergreifender Vorgang" in
    TextBoxM1.Invoke(New BefehlFuerM1(AddressOf BefehleAusThreadNachM1), "Starten")
    Ich habe es auch bereits mit der

    VB.NET-Quellcode

    1. If InvokeRequired Then
    2. End If

    Abfrage versucht, leider ohne Erfolg.
    Vielleicht hat jemand von euch eine Idee und hoffe ich konnte mein Problem halbwegs verständlich erklären.
    Vorab bedanke ich mich schon einmal für eure Hilfe.
    Willkommen im Forum. :thumbup:

    Bolle schrieb:

    ab und an
    ist ein wenig unpräzise.
    "Immer", "Manchmal", "Sehr selten" wären bessere Kandidaten.
    Diese Exception wird immer dann ausgelöst, wenn von einem Thead aus auf ein GUI-Element (schreibend) zugegriffen wird.
    Beim heutigen Multithreading ist es in Multi-Prozessor-Systemen denkbar, dass zwei Threads gleichzeitig auf dasselbe GUI-Element zugreifen und verschiedene Aktionen auslösen, z.B. .Text = "Ja" und .Text = "Nein".
    Da nicht entschieden werden kann, welcher dieser beiden Vorgänge den Vorrabg hat, knallt es, und der Entwickler muss sich selbst darum kümmern.
    Ich weiß nun nicht, was Deine Prozedur macht, deswegen rate ich Dir, das InvokeRequired mal rauszunehmen, bis es an der finalen Codestelle knallt, die sähe dann z.B. so aus: TextBox1.Text = "Ja".
    Wenn Du diese Stelle identifiziert hast, machst Du dies:

    VB.NET-Quellcode

    1. TextBox1.BeginInvoke(Sub() TextBox1.Text = "Ja")
    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!
    Hi
    ich hätte noch ein paar Anmerkungen zum Code:
    • IsBackgroundThread würde ich nur dort auf true setzen, wo auch sichergestellt ist, dass das "einfache Abschießen" des Threads garantiert keinen Schaden anrichtet. Threads beendet man in der Regel am besten so, dass sie einfach das Ende der vom Thread ausgeführten Prozedur erreichen
    • Loop Until My.Settings.HauptthreadAn = False Du verwendest My.Settings aber nicht als private Variablen, oder? Das soll schon über jede Session hinaus so bleiben? Ich würde sowas eher beim Beenden der Form/Anwendung oder beim Ändern der Einstellungen setzen und separat in eine Variablen ablegen, aber ich denke, man kann beides machen. Wenn es als Variablen herhalten soll, dann definitiv über Private ... As Boolean deklarieren. Es sieht allerdings so aus, als könntest du programmieren.

    Ansonsten sieht innerhalb deines Codes eigentlich nichts sonderlich verdächtig aus. Es würde sich definitiv lohnen, wenn du die Stellen ausmachen könntest, an denen die Fehler auftreten. Am einfachsten sollte das gehen, indem du dir die Aufrufliste anschaust und diese ggf. hier postest.

    Was du dir Designtechnisch überlgen könntest, wäre, die Befehle als Delegaten anzulegen und in ein Dictionary zu verpacken. Das gute daran wäre, dass du statt der Select Case-Abfrage einfach dictionary.TryGetValue bzw. dictionary(name) verwenden könntest, um einen Befehl zu erhalten und das somit komplett dynamisch ist.

    Multithreading war übrigens auch schon vor den Multiprozessorsystemen vorhanden, es ist jetzt nur hardwaretechnisch ebenfalls umgesetzt. Es geht bei Windows Forms prinzipiell eigentlich sogar eher darum, dass die Prozesse nacheinander ablaufen und zwar in der Reihenfolge, in der sie in der Realität geschehen, denke ich. Es müsste auf jeden Fall dafür gesorgt werden, dass die Synchronisation der Threads korrekt geschieht, aber nur für soetwas müsste man das Konzept der Windows Forms nicht in der Art gestalten, wie es jetzt der Fall ist.

    Viele Grüße
    ~blaze~
    Erst einmal vielen Dank für die ausführlichen und zügigen Antworten :thumbsup:

    @RodFromGermany:
    Ich habe bereits jeder Maschine ein eigenes GUI-Element zugewiesen, um diesen gleichzeitigen Zugriff zu verhindern. Ist ein Thread für eine Maschine gestartet wird die Maschine/ Thread gesperrt und nicht noch einmal aufgerufen bis der Thread vollständig beendet ist. Trotzdem kommt es :!: Manchmal :!: zu threadübergreifenden Vorgängen, leider nach keinem zyklischen Muster. Ich möchte auch ungern alle Invoke Aufrufe mit Try and Catch umgeben, verwischt nur das Problem.
    Invoke Required ist bereits abgestellt. Kann ich einem Invoke Aufruf höhere Prioritäten zuzuordnen ? ?( Die Logfiles der Maschinen zeigen im Thread auch einen sequentiellen Ablauf und kein durcheinander bis der Debugger wegen dem Fehler beendet.

    Geht Invoke ohne GUI ?

    Würde mir mit BeginInvoke nicht die Wartezeit verloren gehen bzw. meine Pausen im Thread?


    @Blaze:
    ...wäre, die Befehle als Delegaten anzulegen und in ein Dictionary zu verpacken
    klingt interessant. Habe ich zwar noch nicht gemacht, aber ich werde versuche mir mal ein dummy Beispiel zu erstellen.

    P.S. Mit Events tritt der selbe Fehler :!: Manchmal :!: auf.

    Gruß

    Bolle

    Bolle schrieb:

    um diesen gleichzeitigen Zugriff zu verhindern
    Fein, dass Du das weißt. Bei der "Erfindung" der Multitasking-Programmierung wussten die Erfinder dies nicht, deshalb mussten sie die Software absichern. ;)

    Bolle schrieb:

    Geht Invoke ohne GUI ?
    Ohne GUI-Zugriff brauchst Du kein Invoke, allerdings würde es funktionieren, das heißt, ein (Begin)Invoke zu verwenden, ohne dass InvokeRequired abgefragt wurde, funktioniert.
    Wenn Du weißt, dass Du das aus einem Nebenthread heraus aaufrufst, musst Du InvokeRequired nicht abfragen.
    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!
    ich vermute, dass irgendwo aus einem Thread auf ein Form zugegriffen wird - also sowas:

    VB.NET-Quellcode

    1. Form1.Textbox1.Invoke(...)
    2. 'oder gar
    3. dim frm = New Form1
    4. frm .Textbox1.Invoke(...)
    Solch failt, denn dabei wird ein neues Form1 erstellt, für diesen Thread, und dann hat man 2, und Kuddelmuddel ohne Ende.

    Bolle schrieb:

    Ist ein Thread für eine Maschine gestartet wird die Maschine/ Thread gesperrt und nicht noch einmal aufgerufen bis der Thread vollständig beendet ist.


    Wie muss man sich das vorstellen? Du musst nur Invoke bzw. BeginInvoke aufrufen, um auf diesem, jeweiligen Steuerelement Aktivitäten, wie bspw. Updates, durchzuführen. Mehr ist nicht nötig.

    Viele Grüße
    ~blaze~
    @ ErfinderDesRades
    Wenn ich mir die Verweise anschauen, dann finde ich keinen Zugriff mit

    Form1.Textbox1.Invoke(...)

    aus einem Thread auf meine Form. Alles nur wie oben beschrieben über

    VB.NET-Quellcode

    1. TextBoxM1.Invoke(New BefehlFuerM1(AddressOf BefehleAusThreadNachM1), "Starten")


    Werde vielleicht mal das Invoke durch Begin Invoke ersetzen.

    @blaze~
    Wollte damit sagen, dass ein zweiter Aufruf des Nebenthreads durch den Hauptthread ausgeschlossen ist und somit ggf. ein gleichzeitiger Zugriff auf das selbe GUI ausgeschlossen ist. Der Hauptthread ruft einmal den Nebenthread auf, der dann in Interaktion mit der Maschine tritt und das kann ggf. ein wenig dauern und besteht aus mehreren Befehlen, die über

    VB.NET-Quellcode

    1. TextBoxM1.Invoke(New BefehlFuerM1(AddressOf BefehleAusThreadNachM1), "Starten")
    2. TextBoxM1.Invoke(New BefehlFuerM1(AddressOf BefehleAusThreadNachM1), "Anfahren")
    3. TextBoxM1.Invoke(New BefehlFuerM1(AddressOf BefehleAusThreadNachM1), "Zuruecksetzten")
    4. TextBoxM1.Invoke(New BefehlFuerM1(AddressOf BefehleAusThreadNachM1), "...")


    mitgeteilt werden. Erste wenn das Ende erreicht wurde wird die Freigabe des Nebethreads wieder erteilt.

    Bolle schrieb:

    und somit ggf. ein gleichzeitiger Zugriff auf das selbe GUI ausgeschlossen ist
    Das wird dqadurch ausgeschlossen, dass Du gezwungen wirst, Invoke zu verwenden.
    Warum legst Du jedes Mal eine neue BefehlFuerM1-Instanz an?
    Wie wird in BefehleAusThreadNachM1 auf die GUI zugegriffen?
    Mach das Invoke doch lieber dort.
    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
    In BefehleAusThreadNachM1 sende ich per TCPIP Nachrichten an die Maschine, erstelle Logfiles und protokolliere dies in einer TextBox
    Ausgelöst wird der Vorgang aus dem Nebenthread über Invoke. Der Befehl wird wie oben beschrieben in der Case Anweisung selektiert und anschließen weitergeleitet:
    0) Hauptthread ruft Nebenthread auf und sperrt diesen, bis die Freigabe wieder kommt
    1)Nebenthread: ruft über
    TextBoxM1.Invoke(New BefehlFuerM1(AddressOf BefehleAusThreadNachM1), "Starten")
    die Sub auf
    2)

    VB.NET-Quellcode

    1. Public Sub BefehleAusThreadNachM1(ByVal Befehl As String)
    2. Select Case Befehl
    3. Case "Starten"
    4. M1TCPIP.Starten()
    5. Case "Anfahren"
    6. M1TCPIP.Anfahren()
    7. ...
    8. end Sub
    9. Public Class M1TCPIP
    10. Public Sub Anfahren()
    11. client_send("@@50")
    12. end Sub
    13. Sub client_send(ByVal text1 As String)
    14. If Client.Connected Then
    15. streamw.WriteLine(text1)
    16. GesendeteTelegrammeFuellen(text1)
    17. GesendeteNachrichten(text1)
    18. Try
    19. streamw.Flush()
    20. Catch ex As Exception
    21. End Try
    22. End Sub
    23. end class



    @~blaze~
    Es hängt immer am Invoke Befehl. Natürlich wiederholen sich Exceptions, aber den Befehl verwende häufig und daher ist es nicht immer die selbe Stelle.
    @Bolle Falsche Herangehensweise.
    In dem Moment, wo Du den TCP-Part invokest, wird der im Hauptthread ausgeführt, was er ja gar nicht soll.
    Pack ausschließlich den GUI-Part in ein Invoke, nicht aber den TCP-Part (wenn das die richtigen Zeilen sind):

    VB.NET-Quellcode

    1. Me.Invoke(Sub()
    2. GesendeteTelegrammeFuellen(text1)
    3. GesendeteNachrichten(text1)
    4. 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!
    Wenn ich das invoke nur auf das GUI beschränke, dann ist allerdings mein Client geschlossen! Wie bekomme ich dann die Befehle an die Maschine gesendet. Muss ich für jeden Thread dann die Verbindung erneut öffnen?

    VB.NET-Quellcode

    1. If Client.Connected Then

    ist false

    Gestartet wird die TCP Verbindung über das aktivieren einer Checkbox auf der Hauptform

    VB.NET-Quellcode

    1. Private Sub CheckBox1_CheckedChanged(sender As System.Object, e As System.EventArgs) Handles CheckBox1.CheckedChanged
    2. M2TCPIP.TCPIPAutomatikmodus()
    3. End Sub
    4. Public Class M2TCPIP
    5. Public Sub TCPIPAutomatikmodus()
    6. If Client.Connected Then
    7. Else
    8. Client = New System.Net.Sockets.TcpClient
    9. Client.Connect(IP, Port)
    10. Deklare_Streams()
    11. Dim Empfangen As New Threading.Thread(AddressOf listen)
    12. Empfangen.Start()
    13. Empfangen.IsBackground = True
    14. End If
    15. End Sub
    16. End Class




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

    @Bolle Kannst Du mal ein kleines Testprojekt machen und anhängen, das Deinen Effekt reproduziert?
    Und dazu schreiben, was getan werden muss, dass es zum Effekt kommt.
    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!
    Das Problem entsteht, wenn die TCPIP Verbindung der Maschine nicht über die Hauptform geöffnet wird, sondern über die Form der Maschine. Der Hauptthread, der ja den Nebenthread über die Hauptform startet hat dann eine geschlossene TCPIP Verbindung der Maschine. Wenn ich den Client der Maschine über die Hauptform öffne, dann ist die Verbindung in allen Threads der Hauptform geöffnet. Komisch das es häufig über Invoke klappt und dann manchmal ein Fehler auftritt.
    Es bleibt mir wohl nichts anderes übrig, als den TCPIP Client über die Hauptform zu öffnen oder?
    @Bolle ich kann mir nur nicht vorstellen, was das mit threadübergreifender Vorgang zu tun hat.
    Auf welchen Rechnern liegen die beiden Formen?
    Wer ist Server, wer ist Client?
    Üblicherweise läuft ein Server permanent und ein Client meldet sich bei ihm an.
    In diesem Kontext kann ich mir Deine Philosophie nur schlecht vorstellen.
    Kannst Du sie mal etwas näher beschreiben?
    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!