ProgressChanged des BackgroundWorker wird im falschen Thread ausgelöst

  • VB.NET

Es gibt 10 Antworten in diesem Thema. Der letzte Beitrag () ist von isturiel.

    ProgressChanged des BackgroundWorker wird im falschen Thread ausgelöst

    Zum Gruße allerseits,

    da ich meine Probleme hatte eine Statusaktualisierung des mit Hilfe eines BackgroundWorker im Hintergrund laufenden Prozesses auf der GUI meiner Windows Forms-Anwendung auszugeben, habe ich mir eine Anwendung erstellt, die diesem Problem auf die Schliche kommen sollte.

    Zunächst einmal das funktionierende Beispiel:

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    3. Debug.Print("form 1 - dowork : " & CStr(Threading.Thread.CurrentThread.ManagedThreadId))
    4. StartTest() End Sub
    5. Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
    6. Debug.Print("form 1 - progress : " & CStr(Threading.Thread.CurrentThread.ManagedThreadId))
    7. Label1.Text = e.ProgressPercentage
    8. End Sub
    9. Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
    10. Debug.Print("form 1 - completed : " & CStr(Threading.Thread.CurrentThread.ManagedThreadId))
    11. Label1.Text = "fertig"
    12. End Sub
    13. Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    14. Debug.Print("form 1 - startclick : " & CStr(Threading.Thread.CurrentThread.ManagedThreadId))
    15. BackgroundWorker1.RunWorkerAsync()
    16. End Sub
    17. Private Sub StartTest()
    18. Debug.Print("form 1 - starttest : " & CStr(Threading.Thread.CurrentThread.ManagedThreadId))
    19. For i As Integer = 1 To 5
    20. BackgroundWorker1.ReportProgress(i * 20)
    21. Threading.Thread.Sleep(250)
    22. Next
    23. End Sub
    24. End Class


    Meine "ausführende" Prozedur StartTest(), die ReportProgress aufruft, befindet sich mit der Klasse der Form.
    So funktioniert alles, die ausgegebenen ThreadIDs sind wie erwartet:



    Wenn jetzt aber die Funktion StartTest() auslagern möchte in ein Modul (Ich möchte die Form-Klasse möglichst frei von den eigentlichen "Berechnungen" halten) wird das ProgressChanged-Event plötzlich im Thread des BackGroundWorkers ausgelöst. Und damit wird auch das Label nicht aktualisiert.

    VB.NET-Quellcode

    1. Module Module1
    2. Public Sub StartTest()
    3. Debug.Print("form 1 - starttest : " & CStr(Threading.Thread.CurrentThread.ManagedThreadId))
    4. For i As Integer = 1 To 5
    5. Form1.BackgroundWorker1.ReportProgress(i * 20)
    6. Threading.Thread.Sleep(250)
    7. Next
    8. End Sub
    9. End Module





    Mir ist nicht klar, ob das so beabsichtigt ist, und wenn ja warum.

    Natürlich könnte ich mein Programm so ummodeln, dass ich alles in die Klasse der Form setze, aber prinzipiell widerstrebt mir das und ich möchte es natürlich verstehen.

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

    erstmal VB-Tag richtig benutzen
    Wenn mans richtig macht, dann sieht der Code im Forum identisch aus, wie du ihn in deim VisualStudio hast.
    Also bitte editiere deinen Post und paste den Code nochmal neu aussm VS in den Beitrag ein.



    Inhaltlich:
    in Module1:

    VB.NET-Quellcode

    1. Form1.BackgroundWorker1.ReportProgress(i * 20)
    Dassis Crap

    Form1 ist kein Objekt, sondern eine Klasse, und bei Multithreading fährste mit diesem Geschmuddel gegen die Wand. Gugge auch VeryBasics

    ErfinderDesRades schrieb:


    In meinem Beitrag wurde bereits der VB-Tag genutzt, wenn der ebenfalls nicht funzt kann ich es nicht ändern. Im übrigen wäre es dann wohl sinnvoll nicht-funktionierende Tags zu entfernen.



    Inhaltlich:
    in Module1:

    VB.NET-Quellcode

    1. Form1.BackgroundWorker1.ReportProgress(i * 20)
    Dassis Crap

    Form1 ist kein Objekt, sondern eine Klasse, und bei Multithreading fährste mit diesem Geschmuddel gegen die Wand. Gugge auch VeryBasics


    Dass dies ist eine Klasse ist ist mir durchaus bewusst, wie auch mehrfach namentlich erwähnt. Ich habe mir nicht die Mühe gemacht für einen simplen Test auf formale Aspekte Rücksicht zu nehmen.
    Die Probleme beim Multithreading habe ich mitbekommen. Mich interessieren die Gründe, nicht wage Formulierungen.

    isturiel schrieb:

    Ich habe mir nicht die Mühe gemacht für einen simplen Test auf formale Aspekte Rücksicht zu nehmen.

    Solltest du in diesem Fall aber.

    jmcilhinney.blogspot.de/2009/0…fault-form-instances.html
    3. You need to access a form from a secondary thread.

    In order to access a form from a secondary thread you generally need to test its InvokeRequired property and then call its Invoke method. I said earlier that there is only ever one default instance of a form class. That’s not strictly true. In fact, default instances are thread-specific, so there is only ever one default instance per thread.


    Ergo: Wenn man mit multithreading arbeitet, dann NICHT mehr die standardinstanzen verwenden!

    isturiel schrieb:

    In meinem Beitrag wurde bereits der VB-Tag genutzt, wenn der ebenfalls nicht funzt kann ich es nicht ändern. Im übrigen wäre es dann wohl sinnvoll nicht-funktionierende Tags zu entfernen.

    Es geht darum, den VB-Tag richtig zu benutzen - ist das wirklich zuviel verlangt?

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    3. Debug.Print("form 1 - dowork : " & CStr(Threading.Thread.CurrentThread.ManagedThreadId))
    4. StartTest()
    5. End Sub
    6. Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
    7. Debug.Print("form 1 - progress : " & CStr(Threading.Thread.CurrentThread.ManagedThreadId))
    8. Label1.Text = e.ProgressPercentage
    9. End Sub
    10. Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
    11. Debug.Print("form 1 - completed : " & CStr(Threading.Thread.CurrentThread.ManagedThreadId))
    12. Label1.Text = "fertig"
    13. End Sub
    14. Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    15. Debug.Print("form 1 - startclick : " & CStr(Threading.Thread.CurrentThread.ManagedThreadId))
    16. BackgroundWorker1.RunWorkerAsync()
    17. End Sub
    18. Private Sub StartTest()
    19. Debug.Print("form 1 - starttest : " & CStr(Threading.Thread.CurrentThread.ManagedThreadId))
    20. For i As Integer = 1 To 5
    21. BackgroundWorker1.ReportProgress(i * 20)
    22. Threading.Thread.Sleep(250)
    23. Next
    24. End Sub
    25. End Class
    Dazu muß man dem gegebenen Link - VB-Tag richtig benutzen - einfach mal folgen und sorgfältig durchlesen.

    Ebenso VeryBasics.
    Das hat nix mit vagen Vermutungen zu tun, sondern es geht darum, den Unterschied von Klasse und Objekt zu schnackeln, und dann ist klar ersichtlich, dass

    VB.NET-Quellcode

    1. Form1.BackgroundWorker1.ReportProgress(i * 20)
    einfach syntaxwidrig ist.

    Weiters wird dort ausgeführt, warums trotzdem funzt, und unter welchen Umständen man damit vor die Wand fährt. Einer dieser Umstände ist eben Asynchronizität.
    Denn die problematische Zeile im NebenThread ausgeführt greift nicht auf die verborgene Default-Instanz des Haupt-Threads zu, sondern der NebenThread erstellt sich eine eigene Default-Instanz, um darauf zuzugreifen - aber diese 2. Default-Instanz ist natürlich nicht diejenige, die du siehst.
    Ich denke mal, dass Threading.Thread.CurrentThread.ManagedThreadId hier völlig Fehl am Platz ist.
    Dies gibt Dir doch immer den Windows-Thread von Form1 aus, nicht aber den vom BackgroundWorker.
    Die Events des BGW sind doch bereits invoked, also vom Thread des BGW in den Thread von Form1 "getunnelt".
    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!
    Alles klar, nu hab ichs kapiert.

    Danke an picoflop und ErfinderDesRades, der Link ist gut und die Erklärung mit der zweiten Default-Instanz macht das glasklar.

    Ich habe das Ganze mit einer eigenen instanzierten Form-Klasse getestet und es funzt wunderbar. Dass Konzept der Standardinstanz war mir nicht bekannt und werde diese zukünftig meiden.

    Aber eine Frage hätte ich dennoch bzgl.

    VB.NET-Quellcode

    1. Form1.BackgroundWorker1.ReportProgress(i * 20)


    Wird prinzipiell durch den Compiler eine Instanz der Klasse Form1 erzeugt und funzt es daher? Wenn ich das richtig sehe gibt es somit keinen korrekten Syntax um in einer 0815-WinForms-Anwendung von außerhalb der Default-Form (Code in Module auslagern, etc.) auf diese zuzugreifen, oder? Also sprich möchte ich es sauber machen muss ich das Konzept, dass mir VS2010 bietet, über Board werfen und sowieso nur mit explizit instanzierten Formen arbeiten.

    isturiel schrieb:

    Wenn ich das richtig sehe gibt es somit keinen korrekten Syntax um in einer 0815-WinForms-Anwendung von außerhalb der Default-Form (Code in Module auslagern, etc.) auf diese zuzugreifen, oder?

    Doch. Der "Verwender" erhält einen Verweis auf den "Anbieter".

    VB.NET-Quellcode

    1. Public Class Foo
    2. Private _ref as Form
    3. Public Sub New(ref as Form)
    4. _ref = ref
    5. EndSub
    6. Public Sub DoSomething()
    7. _ref.Show
    8. End Sub


    Der Aufruf "FormC.BackgroundworkerY.ReportProgress" ist im übrigen schon per se Humbug, da "sender" in DoWork den korrekten BGW enthält. Das Object castet man auf Backgroundworker und ruft dann dessen .ReportProgress auf. Ruft man aus DoWork eine andere Methode auf, die ReportProgress verwenden soll, so übergibt man halt einen Verweis auf den korrekten BGW an diese Methode.

    isturiel schrieb:

    VB.NET-Quellcode

    1. Form1.
    Form1.Irgendwas ist ein Rudiment aus der grauen VB6-Zeit, da wird einfach eine implizite Instanz erzeugt und genutzt.
    Ein solcher Aufruf funktioniert nicht in Nicht-Form-Klassen.
    Also: Immer ordentlich Instanzen erzeugen und benutzen.
    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!

    isturiel schrieb:

    Wenn ich das richtig sehe gibt es somit keinen korrekten Syntax um in einer 0815-WinForms-Anwendung von außerhalb der Default-Form (Code in Module auslagern, etc.) auf diese zuzugreifen, oder? Also sprich möchte ich es sauber machen muss ich das Konzept, dass mir VS2010 bietet, über Board werfen und sowieso nur mit explizit instanzierten Formen arbeiten.

    Das ist in VeryBasics auch besprochen.
    Man muß sich klarmachen, dass ein objektorientiertes Programm ein Baum von Objekten ist, und dass es 2 grundverschiedene Kommunikations-Arten gibt:
    • TopDown: Der Owner eines SubObjekts kennt natürlich das SubObjekt und hat direkt Zugriff auf dessen Public Properties und Methoden
    • BottomUp: Das SubObjekt kennt den Owner nicht! (Beachte: Widerspruch zu Picoflops Ansatz)
      Stattdessen sendet es Events, die der Owner empfangen kann.
      IMO ist das der sauberere Ansatz, weil dabei die gegenseitige Kopplung der Klassen vermieden wird.

    ErfinderDesRades schrieb:

    [*]BottomUp: Das SubObjekt kennt den Owner nicht! (Beachte: Widerspruch zu Picoflops Ansatz)
    Stattdessen sendet es Events, die der Owner empfangen kann.
    IMO ist das der sauberere Ansatz, weil dabei die gegenseitige Kopplung der Klassen vermieden wird.

    Jupp, ist meiner Meinung nach schon der schönere Ansatz, da ich nicht jeder Unterklasse notwendige Objekte im Aufruf mitgeben muss und es daher sicherlich nur performanter sein kann.

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