Zur Laufzeit erzeugte Controls in einem anderen Thread ansprechen

  • VB.NET

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

    Zur Laufzeit erzeugte Controls in einem anderen Thread ansprechen

    Hi Leute,

    mit ach und krach habe ich es geschafft (dank VB-Paradise und Co.) halbwegs Multithreading zu verstehen.
    Auch ein Testprojekt auf die Beine gestellt.

    Nun was funktioniert ist:
    Mit dem Designer Ovalshapes gezeichnet und benannt.
    Diese sammele ich in List(Of Ovalshape), je in einem eigenen Thread.
    Die kann ich auch manipuieren, wei Backgroundcolor etc...
    Kurzer Code Einsicht:

    VB.NET-Quellcode

    1. Imports Microsoft.VisualBasic.PowerPacks
    2. Public Class frmSPS
    3. Dim osInputs As List(Of OvalShape)
    4. Dim SPS_IP As String = "192.168.1.100"
    5. Dim SPS_Link_Inputs As clsLibNoDave 'LibNoDave eine .dll zum Verbindungsaufbau zur einer SPS
    6. Dim Inputs_Thread As System.Threading.Thread
    7. Private Sub frmSPS_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    8. Control.CheckForIllegalCrossThreadCalls = False
    9. Inputs_Thread = New System.Threading.Thread(AddressOf InputsList)
    10. Inputs_Thread.Start()
    11. End Sub
    12. Public Sub InputsList()
    13. osInputs = New List(Of OvalShape)
    14. osInputs.AddRange({OS_E00, OS_E01, OS_E02, OS_E03, OS_E04, OS_E05, OS_E06, OS_E07})
    15. osInputs.AddRange({OS_E10, OS_E11, OS_E12, OS_E13, OS_E14, OS_E15, OS_E16, OS_E17})
    16. osInputs.AddRange({OS_E20, OS_E21, OS_E22, OS_E23, OS_E24, OS_E25, OS_E26, OS_E27})
    17. osInputs.AddRange({OS_E30, OS_E31, OS_E32, OS_E33, OS_E34, OS_E35, OS_E36, OS_E37})
    18. osInputs.AddRange({OS_E40, OS_E41, OS_E42, OS_E43, OS_E44, OS_E45, OS_E46, OS_E47})
    19. osInputs.AddRange({OS_E50, OS_E51, OS_E52, OS_E53, OS_E54, OS_E55, OS_E56, OS_E57})
    20. osInputs.AddRange({OS_E60, OS_E61, OS_E62, OS_E63, OS_E64, OS_E65, OS_E66, OS_E67})
    21. osInputs.AddRange({OS_E70, OS_E71, OS_E72, OS_E73, OS_E74, OS_E75, OS_E76, OS_E77})
    22. osInputs.AddRange({OS_E80, OS_E81, OS_E82, OS_E83, OS_E84, OS_E85, OS_E86, OS_E87})
    23. osInputs.AddRange({OS_E90, OS_E91, OS_E92, OS_E93, OS_E94, OS_E95, OS_E96, OS_E97})
    24. SPS_Inputs()
    25. End Sub
    26. Public Sub SPS_Inputs()
    27. SPS_Connection_Inputs()
    28. Do
    29. Dim i = 0
    30. Dim bit(7) As Boolean
    31. For iByte As Integer = 0 To 9 'Eingangsbytes 0 - 9
    32. SPS_Link_Inputs.EB(iByte, bit)
    33. For iBit As Integer = 0 To 7
    34. If bit(iBit) Then
    35. osInputs(i).FillColor = Color.LimeGreen
    36. Else
    37. osInputs(i).FillColor = Color.WhiteSmoke
    38. End If
    39. i += 1
    40. Next
    41. Next
    42. Loop
    43. End Sub
    44. Private Sub SPS_Connection_Inputs()
    45. SPS_Link_Inputs = New clsLibNoDave
    46. SPS_Link_Inputs.Connect(SPS_IP)
    47. End Sub


    Das funktioniert auch einwandfrei. Desweiteren gibt es noch einen Thread für die Ausgänge, einen Thread für DB1 und einen Thread für DB2...

    Nun in meinem Hauptprojekt weiß ich nicht welche und wieviele Eingänge ich abfrage muss, deshalb erzeuge ich zur Laufzeit anhand einer DataSet die Eingänge (allerding als Panels nicht mit Ovalshape), d.H. *.xml -> DataSet -> Eingänge Info
    Die zur Laufzeit erzeugten Panels werden auf/in einer PictureBox gezeichnet. Die Zustände der Panels (also eig. nur BackGroundImage) sollen sich ändern,
    d.H. SPS Eingang 0.0 = 1 -> Panel E0.0 = BackGroundImage (SensorEin.jpg (grün)) ansonsten wenn SPS Eingang 0.0 = 0 -> Panel E0.0 = BackGroundImage (SensorAus.jpg (gelb)) SensorEin und SensorAus sind selbst gezeichnete .jpg's...

    Ich habe es schonmal hinbekommen, dass die Panels in einem extra Thread gezeichnet werden, aber sobald ich dann in der nächsten Sub die SPS-Verbindung aufbauen will um den Status(BackGroundImage) der Panels anzupassen hängt das Programm sich auf OHNE Fehlermeldung auf. Dann kann ich nur noch über "strg" + "alt" + "entf" -> Taskmanager abbrechen.

    Hier mal ein Teil vom Code:

    VB.NET-Quellcode

    1. Delegate Sub PicMV_Load([picmv] As String)
    2. Dim SPS_Link_Inputs As clsLibNoDave
    3. Private Sub frmMain_Load(sender As Object, e As System.EventArgs) Handles Me.Load
    4. Me.Inputs_Thread = New Threading.Thread(New ThreadStart(AddressOf Me.InputsList))
    5. Me.Inputs_Thread.Start()
    6. End Sub
    7. Private Sub InputsList()
    8. Me.LoadXML(MVPic)
    9. End Sub
    10. Public Sub LoadXML(ByVal [picmv] As String)
    11. If Me.PicMV.InvokeRequired Then
    12. Dim d As New PicMV_Load(AddressOf LoadXML)
    13. Me.Invoke(d, New Object() {[picmv]})
    14. Else
    15. Sensor_DataSet.Sensor_Table.Clear()
    16. Me.Sensor_DataSet.ReadXml(DataFile)
    17. Me.PicMV.Image = System.Drawing.Image.FromFile([picmv])
    18. Me.PicMV.Refresh()
    19. For Each row In Sensor_DataSet.Sensor_Table
    20. Dim newSensor As New Panel
    21. Dim loc, locProzet As Point
    22. locProzet.X = CInt(100 / CDbl(row.P_Width) * CDbl(row.Left)) ' Diese Berechnungen sind nur dafür da,
    23. locProzet.Y = CInt(100 / CDbl(row.P_Height) * CDbl(row.Top)) ' weil die vorher erzeugte .xml Datei in einer anderen Auflösung erzeugt wird
    24. loc.X = CInt((Me.PicMV.Width / 100 * locProzet.X) + (SensGr / 2)) ' Die PictureBox ist wesentlich kleiner hier
    25. loc.Y = CInt((Me.PicMV.Height / 100 * locProzet.Y) + (SensGr / 2)) ' Koordinaten werden wieder in Pixel umgerechnet.
    26. newSensor = newpanel.CreatePanel(SensGr, Me.PicMV.PointToScreen(Me.PicMV.PointToClient(loc)), SensorAus) 'CreatePanel Klasse (unwichtig)
    27. newSensor.Tag = row.Inv
    28. newSensor.Name = row.BMK
    29. Me.PicMV.Controls.Add(newSensor)
    30. newSensor.BringToFront()
    31. AddHandler newSensor.Paint, AddressOf newSensor_paint
    32. Next
    33. SPS_Inputs()
    34. End If
    35. End Sub
    36. Public Sub SPS_Inputs()
    37. SPS_Connection_Inputs()
    38. Do
    39. Dim EB As String
    40. For Each Sens As Panel In PicMV.Controls()
    41. EB = Strings.Mid(Sens.Name, 2)
    42. Dim SPS_Port() = Split(EB, ".")
    43. Dim Invert = CType(Sens.Tag, Boolean)
    44. If SPS_Link_Inputs.Eingang(CInt(SPS_Port(0)), CInt(SPS_Port(1))) Then
    45. If Invert Then
    46. Sens.BackgroundImage = SensorAus
    47. Else
    48. Sens.BackgroundImage = SensorEin
    49. End If
    50. Else
    51. If Invert Then
    52. Sens.BackgroundImage = SensorEin
    53. Else
    54. Sens.BackgroundImage = SensorAus
    55. End If
    56. End If
    57. Next
    58. Loop
    59. End Sub
    60. Public Sub SPS_Connection_Inputs()
    61. SPS_Link_Inputs = New clsLibNoDave
    62. SPS_Link_Inputs.Connect(SPS_IP)
    63. End Sub


    Vom Prinzip ist das ja das selbe wie das Testprojekt was bereit läuft, nur der Unterschied ist das die Panels zur Laufzeit erzeugt werden.
    Was mach ich falsch oder was berücksichtige ich nicht?

    Danke vorab für eure Antworten / Hilfe

    MfG
    newbie
    Moin,

    CheckForIllegalCrossThreadCalls ist eine böse Property, wenn man sie auf False setzt, da Fehler einfach ignoriert werden und Du in Gefahr läufst, einen Deadlock zu kassieren und das war's. Mach's richtig und nutze Invoke.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:

    newbie schrieb:

    wenn ich wüsste wie.
    Zum Beispiel:

    VB.NET-Quellcode

    1. Me.Invoke(Sub() Me.TextBox1.Text = "bla")
    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:

    Me.Invoke(Sub() Me.TextBox1.Text = "bla")


    In meinem Fall wäre die Methode...

    VB.NET-Quellcode

    1. 'Public Sub SPS_Inputs() , also
    2. Me.Invoke(SPS_Inputs()...)

    oder?

    Als Object gibt man ein Control an

    RodFromGermany schrieb:

    Me.TextBox1.Text = "bla"


    Welches Object muss ich denn angeben in meinem Fall? Die Controlls (dynamisch erzeugte Panels) die ich in dem "neuen" Thread erstelle sind ja in

    Quellcode

    1. PicMV.Controls()
    Parameter muss man zwangsläufig mitgeben?

    Ich erwarte kein fertigen Code, ich möchte es lediglich verstehen...
    Kann mir jemand bitte auf die Sprünge helfen bitte?

    MfG

    newbie schrieb:

    die Methode...
    Alles was nach Sub() steht, wird im Me-Main-Thread ausgeführt.
    Es gibt die Syntax, dort mehrere Zeilen Code rein zu schreiben ("anonyme Methode"), ich empfehle immer eine Ein-Zeilen.-Syntax, also wie im Beispiel ein Zugriff auf ein Control, oder wie Du es richtig umgesetzt hast ( :thumbup: ) den Aufruf einer Prozedur. Dort kannst Du beliebige Parameter anhängen.
    Einen Return-Wert (Function-Aufruf) gibt es nicht, weil der Aufruf asynchron erfolgt.
    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!
    ...ok....

    bereits vorhandene (d.H. Controls die mit dem Designer gezeichnet worden sind) kann ich auch ansprechen bzw. diese "invoken"

    Bei den dynamisch erzeugten habe ich trotzdem noch eine Lücke ?(

    Meine vorgehensweise, aufgrund meines verständnisses sieht folgendermaßen aus.

    VB.NET-Quellcode

    1. 'Dekleration
    2. Delegate Sub SPS_Eingang()
    3. Private Sub frmMain_Load(sender As Object, e As System.EventArgs) Handles Me.Load
    4. Me.Inputs_Thread = New Threading.Thread(New ThreadStart(AddressOf Me.InputsList))
    5. Me.Inputs_Thread.Start()
    6. End Sub
    7. Private Sub InputsList()
    8. Me.LoadXML(MVPic)
    9. End Sub
    10. Public Sub LoadXML(ByVal [picmv] As String)
    11. If Me.PicMV.InvokeRequired Then
    12. Dim d As New PicMV_Load(AddressOf LoadXML)
    13. Me.Invoke(d, New Object() {[picmv]})
    14. Else
    15. Sensor_DataSet.Sensor_Table.Clear()
    16. Me.Sensor_DataSet.ReadXml(DataFile)
    17. Me.PicMV.Image = System.Drawing.Image.FromFile([picmv])
    18. Me.PicMV.Refresh()
    19. For Each row In Sensor_DataSet.Sensor_Table
    20. 'macht was...
    21. Next
    22. 'SPS_Inputs <--- Der Aufruf der Sub geht nicht.
    23. Dim dSPS As New SPS_Eingang(AddressOf SPS_Inputs)
    24. Me.Invoke(dSPS) 'Ab hier geht es nicht weiter...."Freeze"
    25. End If
    26. End Sub
    27. Public Sub SPS_Inputs()
    28. SPS_Connection_Inputs()
    29. Do
    30. Dim EB As String
    31. For Each Sens As Panel In PicMV.Controls()
    32. EB = Strings.Mid(Sens.Name, 2)
    33. Dim SPS_Port() = Split(EB, ".")
    34. Dim Invert = CType(Sens.Tag, Boolean)
    35. If SPS_Link_Inputs.Eingang(CInt(SPS_Port(0)), CInt(SPS_Port(1))) Then
    36. If Invert Then
    37. Sens.BackgroundImage = SensorAus
    38. Else
    39. Sens.BackgroundImage = SensorEin
    40. End If
    41. Else
    42. If Invert Then
    43. Sens.BackgroundImage = SensorEin
    44. Else
    45. Sens.BackgroundImage = SensorAus
    46. End If
    47. End If
    48. Next
    49. Loop
    50. End Sub
    51. Public Sub SPS_Connection_Inputs()
    52. SPS_Link_Inputs = New clsLibNoDave
    53. SPS_Link_Inputs.Connect(SPS_IP)
    54. End Sub


    Wo ist mein Denkfehler ?( :?:
    mir scheint dein Testprog untauglich.
    Sieht mir aus, als wollest du Daten-Ereignisse verarbeiten, die von einer Sps-Steuerung ausgehen.

    Wenn das stimmt, solltest du eine entsprechende Simulation coden, und das geht nicht mit einem Thread.

    Sondern mit einem Threading.Timer simulierst du, dass die Sps iwelche Daten einbringt.

    Oder du erzählst noch mehr von was du wirklich vorhast - ich hatte nämlich mal was für eine Sps-Steuerung gecodet, die tickte noch ganz anders, sodass Threading ganz üflüssig wurde.
    Die hat nämlich alle Daten gesammelt, und mit einem WinForms-Timer konnte man alle 300ms abholen, was eingetrudelt war - sehr listenreich!
    Diese Zeile ist Böse...

    newbie schrieb:

    Control.CheckForIllegalCrossThreadCalls = False

    Nach dem auskomentieren folgt der Fehler:
    Ein Ausnahmefehler des Typs "System.InvalidOperationException" ist in System.Windows.Forms.dll aufgetreten. Zusätzliche Informationen: Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement ShapeContainer1 erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.
    Mit der o.g. Zeile läuft das Testprogramm siehe Bild(SPS)
    Ich würde das aber gerne richtig verstehen und umsetzen mit den delegaten und Invoke und Co.

    Jetzt bin ich komplett wieder bei NULL

    Ich verstehe das einfach nicht...muss ich jetzt für jedes einzelne Steuerelement einen Invoke ausführen oder was?
    Ich kann mit den ganzen Beispielen im Netz und hier nichts anfangen

    RodFromGermany schrieb:

    VB.NET-Quellcode

    1. Me.Invoke(Sub() Me.TextBox1.Text = "bla")


    Wo schreibe ich

    Quellcode

    1. Me.Invoke
    welche

    Quellcode

    1. Sub()
    ich habe keine TextBox

    Quellcode

    1. Me.TextBox1.Text = "bla"
    sondern 104 Ovalshapes

    ErfinderDesRades schrieb:

    Wenn das stimmt, solltest du eine entsprechende Simulation coden, und das geht nicht mit einem Thread.

    Sondern mit einem Threading.Timer simulierst du, dass die Sps iwelche Daten einbringt.

    Brauche keine Simulation, ich habe eine echte SPS hier, wo ich den Code direkt testen kann.

    Das Testprojekt soll einfach nur schauen was die SPS für Signalzustände hat, nichts weiter. Dafür habe ich vier Threads vorgesehen (hatte es auch mit einem Timer gemacht aber war zu träge)

    VB.NET-Quellcode

    1. Inputs_Thread = New System.Threading.Thread(AddressOf InputsList)
    2. Inputs_Thread.Start()
    3. Threading.Thread.Sleep(100)
    4. Outputs_Thread = New System.Threading.Thread(AddressOf OutputsList)
    5. Outputs_Thread.Start()
    6. Threading.Thread.Sleep(100)
    7. DB1_Thread = New System.Threading.Thread(AddressOf DB1List)
    8. DB1_Thread.Start()
    9. Threading.Thread.Sleep(100)
    10. DB2_Thread = New System.Threading.Thread(AddressOf DB2List)
    11. DB2_Thread.Start()


    Wie geht es weiter?

    MfG
    Bilder
    • SPS.jpg

      215,4 kB, 720×500, 220 mal angesehen

    newbie schrieb:

    ich habe keine TextBox

    newbie schrieb:

    sondern 104 Ovalshapes
    Ich sehe, Du hast die Lösung noch nicht ganz verstanden.
    Wozu machst Du dies?

    newbie schrieb:

    VB.NET-Quellcode

    1. Threading.Thread.Sleep(100)
    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!

    ErfinderDesRades schrieb:

    Das basierte auf der sog.
    DotNetSiemensPLCToolBoxLibrary.dll


    Meinen SPS Zugriff bewerkstellige ich mit LibNoDave.dll, was auch wunderbarbar funktioniert (alledings mit Timer)



    Edit:
    Vergiss die Sleeps, das war nur als abhilfe, weil der Code Fehler ausgelöst hatte.

    VB.NET-Quellcode

    1. Threading.Thread.Sleep(100)




    RodFromGermany schrieb:

    Ich sehe, Du hast die Lösung noch nicht ganz verstanden.


    Leider hast du Recht.


    Eigentlich ist es relative egal was ich vor habe, ich möchte es bloß verstehen und es auch richtig anwenden.
    1. Thread deklarieren Dim Inputs_Thread As System.Threading.Thread
    2. Thread verknüpfen Inputs_Thread = New System.Threading.Thread(AddressOf InputsList)
    3. Thread starten Inputs_Thread.Start()
    4. Delegieren? / Invoke? Da hapert es nun.
    Wie mach ich das sinnvollerweise? Kann mir jemand das mal so erklären das ich es auch verstehe?

    Muss ich einen Delegate Sub haben? Wenn, ja...
    Muss es vier mal sein? Wegen den vier Threads die ich starte.

    An welcher Stelle muss man Invoke einsetzen? Wie? Was macht Invoke genau?

    MfG

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

    newbie schrieb:

    An welcher Stelle muss man Invoke einsetzen? Wie? Was macht Invoke genau?
    Schreibe Deinen Code und greif einfach auf die Controls im Main-Thread zu.
    Wenn dann eine Exception wegen thread-übergfreifendem Zugriff kommt, musst Du genau diese Zeile durch einen Invoke-Zugriff ersetzen.
    Wenn da mehrere Zeilen stehen, in denen ein Zugriff erfolgt, mach da ne Prouedur mit entsprechenden Parametern draus und invoke diese.

    VB.NET-Quellcode

    1. Me.Invoke(Sub() MyInvokingSub(a, b, c, d))
    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!
    kleiner Tipp während Invoke den aufrufenden Thread blockiert tut BeginInvoke dies nicht.
    Außerdem sollten Daten nicht in Controls gehalten werden, wenn es sich um Daten handelt, die nicht nur zur Anzeige da sind, so speichere diese in einer Variable(mit entsprechendem Datentyp), dann kannst du über eine Entsprechende Methode(welche du über Invoke im GUI-Thread aufrufst) die Anzeige der GUI aktuallisieren.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---

    RodFromGermany schrieb:

    Schreibe Deinen Code und greif einfach auf die Controls im Main-Thread zu.
    Wenn dann eine Exception wegen thread-übergfreifendem Zugriff kommt, musst Du genau diese Zeile durch einen Invoke-Zugriff ersetzen.


    Siehe Bild

    osInputs() ist eine List(Of OvalShape) , eine .InvokeRequired geht nicht.

    Wenn es ein Steuerelement wäre könnte ich ...

    VB.NET-Quellcode

    1. Public Sub SPS_Inputs()
    2. If Me.osInputs.InvokeRequired Then
    3. Dim d As New myDelegateSub(AddressOf SPS_Inputs)
    4. Me.Invoke(d, New Object())
    5. Else
    6. 'Mach was
    7. End If
    8. End Sub

    ...oder?

    RodFromGermany schrieb:


    Wenn da mehrere Zeilen stehen, in denen ein Zugriff erfolgt, mach da ne Prouedur mit entsprechenden Parametern draus und invoke diese.

    Wie? Könntest du mir bitte ein konktretes Beisiel darstellen, was sich auf mein Projekt abbilden lässt? Bitte.

    MfG
    Bilder
    • InvaliOperationException.jpg

      763,38 kB, 1.920×1.058, 168 mal angesehen

    newbie schrieb:

    konktretes Beisiel
    So was:

    VB.NET-Quellcode

    1. Public Sub SPS_Inputs()
    2. Dim a = 5
    3. Dim b = 6
    4. Dim c = 12
    5. Me.Invoke(Sub() MyInvokingSub(a, b, c))
    6. End Sub
    7. Private Sub MyInvokingSub(a As Integer, b As Integer, c As Integer)
    8. MessageBox.Show((a + b + c).ToString)
    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!

    ErfinderDesRades schrieb:

    natürlich
    Sir jawoll Sir. ;)
    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!