Zugriff auf Textbox aus Parallel For

  • VB.NET

Es gibt 7 Antworten in diesem Thema. Der letzte Beitrag () ist von ~blaze~.

    Zugriff auf Textbox aus Parallel For

    Hi,

    ich will eine TextBox names logMessage aus einer parallel for Schleife heraus updaten....

    VB.NET-Quellcode

    1. Public Class frmMain()
    2. .....
    3. Delegate Sub updateLogCallback(ByVal data As Object)
    4. Public Sub updateLog(ByVal data As Object)
    5. Dim content As String
    6. If logMessage.InvokeRequired Then
    7. logMessage.Invoke(New updateLogCallback(AddressOf updateLog), data)
    8. Else
    9. Dim message1 = data(0)
    10. Dim message2 = data(1)
    11. content = logMessage.Text
    12. If content.IndexOf(message1 & "...") > 0 Then
    13. content = content.Replace(message1 & "...", message1 & message2)
    14. Else
    15. content = logMessage.Text & vbCrLf & message1 & message2
    16. End If
    17. logMessage.Text = content
    18. End If
    19. End Sub
    20. ....
    21. public Sub Main()
    22. Parallel.For(0, 100, (New ParallelOptions() With {.MaxDegreeOfParallelism = 6}),
    23. Sub(i)
    24. Dim newThread As New Thread(AddressOf updateLog)
    25. newThread.Start(New Object() {"Hallo " , "..."})
    26. End Sub)
    27. End Sub
    28. ....
    29. End Class


    Passiert leider gar nix. Auch keine Fehlermeldung, aber auch kein Update der Textbox..
    Wo liegt der Fehler?

    Merci

    Markus
    Hi
    das sieht nicht ganz richtig aus. Parallel selber verwendet von sich aus eigentlich schon Threads, die musst du nicht erst im Methodenrumpf erzeugen. Die Sub Main hat nichts mit dem Einstiegspunkt der Anwendung zu tun, oder? Also wird die Form angezeigt und Main aufgerufen. Von Main aus wird über Parallel.For eine Schleife von 0 bis 99 durchlaufen und jeweils im asynchronen Teil ein weiterer neuer Thread erzeugt, der dann updateLog ausführt. Was du dann machst ist ein typischer Option Strict Off-Kandidat. Du rufst Werte am Index 0 und 1 eines Objects ab, nicht eines Object-Arrays. Außerdem castest du die Werte dann auch nicht zu String usw.
    Wenn du weißt, dass dein Objekt, das in einer Object-Variable ist, einen bestimmten Typ hat, verwende DirectCast(..., DeinTyp), um auch auf die Sachen zugreifen zu können, die der andere Typ bereitstellt. Also in deinem Fall wäre das DirectCast(data, String())(0), da du ein String-Array übergibst. Wenn Option Infer auf On ist, wird automatisch erkannt, dass es sich bei message1 und message2 um Strings handelt. Wenn nicht, müsstest du As String mit angeben. Schöner wäre übrigens folgendes: Verwende einen StringBuilder, den du vom Thread aus per SyncLock blockierst. Anschließend schreibst du per StringBuilder.Append und StringBuilder.AppendLine die Daten raus. Sobald sich der StringBuilder dann geändert hat und der Task beendet ist, updatest du den TextBox-Inhalt durch den Invoke-Aufruf.

    Achja: Wenn übrigens keine nicht blockierenden Sachen in den parallel ausgeführten Tasks passieren, hast du nichts von der Parallelisierung, da sich die Threads gegenseitig blockieren. Läuft je nach Verwendung evtl. sogar langsamer. Wenn es wirklich nur so kleine Statusupdates sind, reicht häufig ein Art Timer oder Event, das von der Form aus abonniert wird und in das Log schreibt.

    Gruß
    ~blaze~

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

    Anhand welcher Indizien würdest Du denn das Funktionieren des Codes erkennen?
    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!
    Hallo zusammen,

    Danke für die Rückmeldungen. Habe inzwischen die Lösung wie folgt umgesetzt:

    VB.NET-Quellcode

    1. Imports System.Threading
    2. Imports System.Threading.Tasks
    3. Public Class Form1
    4. Shared objLock As New Object
    5. Shared logContent As String = ""
    6. Delegate Sub updateLogMessageCallback()
    7. Public Sub updateLogMessage()
    8. If logMessage.InvokeRequired Then
    9. logMessage.BeginInvoke(New updateLogMessageCallback(AddressOf updateLogMessage))
    10. Return
    11. End If
    12. Me.logMessage.Text = logContent
    13. End Sub
    14. Public Sub updateLog(ByVal data As String())
    15. SyncLock logContent
    16. Dim message1 = DirectCast(data, String())(0)
    17. Dim message2 = DirectCast(data, String())(1)
    18. If logContent.IndexOf(message1 & "...") > 0 Then
    19. logContent = logContent.Replace(message1 & "...", message1 & message2)
    20. Else
    21. logContent = logContent & vbCrLf & message1 & message2
    22. End If
    23. Debug.Print(logContent)
    24. End SyncLock
    25. updateLogMessage()
    26. End Sub
    27. Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    28. Parallel.For(0, 20, (New ParallelOptions() With {.MaxDegreeOfParallelism = Environment.ProcessorCount - 2}),
    29. Sub(i)
    30. updateLog(New String() {"test" & CStr(i), "..."})
    31. 'Debug.Print(CStr(i) & "...")
    32. updateLog(New String() {"test" & CStr(i), "...ready"})
    33. 'Debug.Print(CStr(i) & "...ready")
    34. End Sub)
    35. End Sub
    36. End Class



    Danke nochmal...

    Markus

    ~blaze~ schrieb:

    Also wie gesagt, da bringt's dir nichts, die Vorgänge asynchron laufen zu lassen. Das SyncLock sorgt dafür, dass die Vorgänge aufeinander warten, da die Objekte dadurch bis zum End Synclock vom Thread gesperrt werden.

    Gruß
    ~blaze~


    Hi Blaze,

    hmm, dachte eigentlich das funktioniert. Im meinem Fall liegen zwischen den beiden Ausgaben die eigentliche Aufgabe (die eine recht lange Ausführungszeit benötigt).

    VB.NET-Quellcode

    1. updateLog(New String() {"test" & CStr(i), "..."})
    2. 'Debug.Print(CStr(i) & "...")
    3. '.....long running task
    4. updateLog(New String() {"test" & CStr(i), "...ready"})
    5. 'Debug.Print(CStr(i) & "...ready")


    Es kommt mir vor, als wäre der Value von logContent plausibel - wenngleich logMessage trotz logMessage.refresh() nicht die richtige Ausgabe bringt. Es scheint mir, dass sie tatsächlich von der langen Task behindert wird.
    Aber nochmal, logContent wird meiner Meinung bereits entsprechend des tatsächlichen Taskstatus aktualisiert.

    VB.NET-Quellcode

    1. Delegate Sub updateLogMessageCallback()
    2. Public Sub updateLogMessage()
    3. If logMessage.InvokeRequired Then
    4. logMessage.BeginInvoke(New updateLogMessageCallback(AddressOf updateLogMessage))
    5. Return
    6. End If
    7. Me.logMessage.Text = logContent
    8. Me.logMessage.Refresh()
    9. Application.DoEvents()
    10. End Sub
    11. Public Sub updateLog(ByVal data As String())
    12. SyncLock logContent
    13. Dim message1 = DirectCast(data, String())(0)
    14. Dim message2 = DirectCast(data, String())(1)
    15. If logContent.IndexOf(message1 & "...") > 0 Then
    16. logContent = logContent.Replace(message1 & "...", message1 & message2)
    17. Else
    18. logContent = logContent & vbCrLf & message1 & message2
    19. End If
    20. Debug.Print(logContent)
    21. End SyncLock
    22. updateLogMessage()
    23. End Sub


    Wäre Dir aber für jeden Tip dankbar. Was wäre ein besserer Ansatz? DANKE!

    Grüsse

    Markus

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

    Lege einfach einen einzigen Thread an, der das asynchron ausführt oder verwende System.Threading.ThreadPool.QueueUserWorkItem. In deinem Fall hast du eine große Menge an Threads, die sich dem Problem annimmt. Wenn du stattdessen nur einen einzigen Thread hast, musst du nur zwischen GUI-Thread und Arbeiterthread synchronisieren. Da lohnt sich's dann auch, den StringBuilder zu verwenden und den dann darzustellen. Application.DoEvents ist übrigens unschön.

    Gruß
    ~blaze~