Threads & GUI, aber .invokeRequired nicht möglich

  • VB.NET

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

    Threads & GUI, aber .invokeRequired nicht möglich

    Folgendes vereinfachtes (!) Szenario: (auch als VS 2010-Projekt hier angefügt)
    Es gibt eine Form1, auf dieser befindet sich eine ListBox1 und ein Button1. Dann folgender Code:

    VB.NET-Quellcode

    1. Public Class Form1
    2. ' In diesem Code sollte keine Thread-Behandlung durchgeführt werden.
    3. ' Mir wäre es am liebsten, wenn ich den unten stehenden Code so stehen lassen könnte.
    4. Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    5. Dim meinArbeiter As New Arbeiter
    6. AddHandler meinArbeiter.ZwischenergebnisEingetroffen, AddressOf meinArbeiter_ZwischenergebnisEingetroffen
    7. End Sub
    8. Private Sub meinArbeiter_ZwischenergebnisEingetroffen(ByRef sender As Arbeiter, ByVal ergebnis As String)
    9. ListBox1.Items.Add("Objekt Nr. " & sender.objektID.ToString & " hat Event gefeuert bei " & ergebnis.ToString & ".")
    10. End Sub
    11. End Class

    VB.NET-Quellcode

    1. Public Class Arbeiter
    2. ' Hier sollte die Threadbehandlung laufen. Ich weiss allerdings nicht, wie ich den Code dahingehend ändere.
    3. Public Event ZwischenergebnisEingetroffen(ByRef sender As Arbeiter, ByVal ergebnis As String)
    4. Private erstellteObjekte As Integer
    5. Public objektID As Integer
    6. Public Sub New()
    7. objektID = erstellteObjekte 'Diesem Objekt eine ID geben
    8. erstellteObjekte += 1 'Den Counter erhöhen, weil es jetzt ein neues Objekt von mir gibt
    9. Dim Thread As New Threading.Thread(AddressOf Rechne)
    10. Thread.Start()
    11. End Sub
    12. Public Sub Rechne()
    13. Dim i As Integer
    14. Do While i < 2147483646 'Datentyp Integer geht höchstens bis 2147483647
    15. i += 1
    16. If i Mod 10000 = 0 Then
    17. RaiseEvent ZwischenergebnisEingetroffen(Me, i) 'Immer, wenn i genau durch 10000 teilbar ist, Event feuern
    18. End If
    19. Loop
    20. End Sub
    21. End Class

    Natürlich erscheint jetzt der Fehler:
    Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement ListBox1 erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.
    ...und natürlich könnte ich diesen Fehler beheben, indem ich das in dem Quellcode der Form1 wie folgt mache:

    VB.NET-Quellcode

    1. Private Delegate Sub dlgUpdateUI(ByVal text As String)
    2. Sub updateUI(ByVal text As String)
    3. If ListBox1.InvokeRequired = True Then
    4. Dim d As New dlgUpdateUI(AddressOf updateUI)
    5. ListBox1.Invoke(d, text)
    6. Else
    7. ListBox1.Items.Add(text)
    8. End If
    9. End Sub

    Das will ich aber nicht. Warum? Ich möchte die Klasse anderen zur Verfügung stellen und die sollen keine Probleme mit Deleganten & Co bekommen. Das Problem soll also in der Klasse "Arbeiter" gelöst werden. Nur wie? Threads sind für mich ein wenig Neuland. Mit dem Backgroundworker bekomm ich das ganze auf die Reihe, allerdings kann ich den für meine richtige Klasse nicht mehr nehmen. Hat jemand eine Idee? Evtl. beim Aufgruf von "New" sich merken, von welchem Thread das Objekt erzeugt wurde und bei "RaiseEvent" irgendwie diesen gemerkten Thread wieder benutzen? Wäre für jeden Tipp dankbar, muss auch kein ganzer Lösungsweg sein! :)
    Dateien
    • ThreadTest.zip

      (12,72 kB, 147 mal heruntergeladen, zuletzt: )

    Enixus schrieb:

    Das Problem soll also in der Klasse "Arbeiter" gelöst werden.
    Tja, das wird Dir wohl nicht gelingen.
    Vielleicht solltest Du eine Lambda-Funktion (VS 2010, -> SuFu) erstellen, die das invoken übernimmt.
    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- aufgrund der Stichwörter habe ich eine funktionierende Lösung gefunden. :) Ich versteh jetzt nur nicht mehr, was da eigentlich passiert...

    Ich habe die Klasse "Arbeiter" jetzt folgendermaßen geändert:

    VB.NET-Quellcode

    1. Public Class Arbeiter
    2. Private asyncOp As System.ComponentModel.AsyncOperation
    3. Public Event ZwischenergebnisEingetroffen(ByRef sender As Arbeiter, ByVal ergebnis As String)
    4. Private Shared erstellteObjekte As Integer 'Hier werden alle erstellen Instanzen von mir gezählt.
    5. Public objektID As Integer
    6. Public Sub New()
    7. asyncOp = System.ComponentModel.AsyncOperationManager.CreateOperation(Nothing) '(1) Was passiert hier? Warum "Nothing"?
    8. objektID = erstellteObjekte 'Diesem Objekt eine ID geben
    9. erstellteObjekte += 1 'Den Counter erhöhen, weil es jetzt eine neue Instanz von mir gibt
    10. Dim Thread As New Threading.Thread(AddressOf Rechne)
    11. Thread.Start()
    12. End Sub
    13. Public Sub Rechne()
    14. Dim i As Integer
    15. Do While i < 2147483646 'Datentyp Integer geht höchstens bis 2147483647
    16. i += 1
    17. If i Mod 10000000 = 0 Then
    18. asyncOp.Post(New Threading.SendOrPostCallback(AddressOf raiseEventHelper), i) '(2) Was macht SendOrPostCallback genau? Die Argument-Übergabe selbst verstehe ich...
    19. End If
    20. Loop
    21. End Sub
    22. Public Sub raiseEventHelper(ByVal teilergebnis As Integer)
    23. RaiseEvent ZwischenergebnisEingetroffen(Me, teilergebnis)
    24. End Sub
    25. End Class

    Das ganze funktioniert! :) Das letzte RaiseEvent wird wirklich vom GUI-Thread aus gefeuert. Aber warum??? So toll das auch ist, so gerne würde ich aber verstehen, was da vor sich geht. Was macht "SendOrPostCallback"? Was macht "asyncOp" die ganze Zeit über? Die MSDN-Hilfen sind irgendwie sehr verwirrend diesbezüglich... (Anbei als Anlage das funktionierende Mini-Projekt)
    Dateien
    • ThreadTest2.zip

      (12,85 kB, 154 mal heruntergeladen, zuletzt: )

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

    Einfacher IMHO:

    VB.NET-Quellcode

    1. Public Class EventRaiser
    2. Public Event foo()
    3. Private Sub RaiseMyEventRight()
    4. For Each d As [Delegate] In fooEvent.GetInvocationList
    5. If TypeOf d.Target Is Control AndAlso DirectCast(d.Target, Control).InvokeRequired Then
    6. DirectCast(d.Target, Control).BeginInvoke(fooEvent)
    7. Else
    8. RaiseEvent foo()
    9. End If
    10. Next
    11. End Sub
    12. Public Sub DoIt()
    13. Dim t As New Threading.Thread(AddressOf RaiseMyEventRight)
    14. t.Start()
    15. End Sub
    16. End Class


    Benutzung:

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private WithEvents er As New EventRaiser
    3. Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    4. er.DoIt()
    5. End Sub
    6. Private Sub er_foo() Handles er.foo
    7. Button1.Text = "geht doch!"
    8. End Sub
    9. End Class
    @Enixus:

    Zu Frage 1: Diesem Parameter kannst du ein beliebiges Objekt übergeben. Es wird im resultierenden AsyncOperation-Objekt in der Eigenschaft UserSuppliedState zur Verfügung gestellt und dient dazu, das Objekt eindeutig zu identifizieren.

    Zu Frage 2: Die Post-Methode nimmt eine Instanz des SendOrPostCallback-Delegaten an. Bei dir zeigt dieser Delegat auf raiseEventHelper. Im MSDN steht, was diese Methode exakt tut:
    Invokes a delegate on the thread or context appropriate for the application model.

    Sie ruft also den übergebenen Delegaten im "richtigen" Thread auf. Die nächste Frage ist bestimmt, woher die Methode den richtigen Thread kennt. Den ermittelt sie heimlich still und leise im Konstruktor von Arbeiter, genauer im Aufruf von CreateOperation(). Der Konstruktor wird mit dem UI-Thread ausgeführt. Das AsyncOperation-Objekt merkt sich das und führt den der Post-Methode übergebenen Delegaten in diesem Thread aus.

    [Zusatzinfo: Das funktioniert über die Windows API per PostMessage - der Aufrufer registriert den Delegaten im System und sendet eine Window-Message mit PostMessage an das Zielfenster (das ja mit dem passenden Thread ausgeführt wird). Der UI-Thread, der auch die Nachrichtenschleife ausführt, empfängt diese Nachricht und führt den Delegaten aus. Das Ganze lässt sich im Reflector ganz gut sehen.]

    Wichtig ist aber folgendes: Deine Methode funktioniert nicht, wenn du die Klasse "Arbeiter" nicht mit dem UI-Thread erstellst. Daher solltest du die Methode von picoflop vorziehen. Diese arbeitet intern fast genauso (sehr viel umfangreicher) wie du es selbst programmiert hast und wie in der Zusatzinfo beschrieben - mit einem wichtigen Unterschied: Das Target wird explizit angegeben (und nicht implizit im Arbeiter-Konstruktor ermittelt). Das bedeutet, dass picoflops Variante den UI-Thread immer findet und deine nur, wenn du die Arbeiter-Klasse korrekt aufrufst. Wenn du die Bibliothek verteilen willst, ist das noch viel wichtiger, denn deine User könnten die Arbeiter-Klasse nicht mit dem UI-Thread erzeugen, was wahrscheinlich zum Crash (IllegalCrossThreadCall) führt.
    Gruß
    hal2000
    Erstmal: Ein dickes fettes Dankeschön an Picoflop und hal2000! Habe von euch jetzt schon eine Menge gelernt. Werde jetzt wohl wirklich deine Variante nehmen, Picoflop!

    Trotzdem möchte ich noch einmal nachhaken, weil ich da noch ein/zwei kleine Verständnisprobleme habe, gerade zu deinen Ausführungen, hal2000...
    Es geht um folgendes:
    Wichtig ist aber folgendes: Deine Methode funktioniert nicht, wenn du die Klasse "Arbeiter" nicht mit dem UI-Thread erstellst. (...) Diese arbeitet intern fast genauso (sehr viel umfangreicher) wie du es selbst programmiert hast und wie in der Zusatzinfo beschrieben - mit einem wichtigen Unterschied: Das Target wird explizit angegeben (und nicht implizit im Arbeiter-Konstruktor ermittelt). Das bedeutet, dass picoflops Variante den UI-Thread immer findet und deine nur, wenn du die Arbeiter-Klasse korrekt aufrufst. Wenn du die Bibliothek verteilen willst, ist das noch viel wichtiger, denn deine User könnten die Arbeiter-Klasse nicht mit dem UI-Thread erzeugen, was wahrscheinlich zum Crash (IllegalCrossThreadCall) führt.


    Ich kann jedoch keinen richtigen Unterschied unterm Strich zwischen meinem Code und Picoflops Code feststellen.
    Mein Code:
    Beispiel 1: GUI-Thread erstellt Instanz von Arbeiter. Arbeiter-Thread merkt sich GUI-Thread. Event wird mittels GUI-Thread gefeuert. Zugriff auf GUI direkt möglich. Alles i.O.
    Beispiel 2: GUI-Thread erstellt einen weiteren Thread, nennen wir ihn SinnlosThread. SinnlosThread erstellt Instanz von Arbeiter-Thread. ArbeiterThread merkt sich SinnlosThread. Event wird mittels SinnlosThread gefangen. Kein Zugriff auf GUI direkt möglich, da ich mich nicht im GUI-Thread befinde.

    Picoflops Code:
    Beispiel 1: GUI-Thread erstellt Instanz von Arbeiter. Arbeiter-Thread schaut, ob die Event-Empfangende Klasse ein Control ist und ob ein Aufruf vom GUI-Thread erforderlich ist. Da das der Fall ist fängt der GUI-Thread das Event. Zugriff auf GUI direkt möglich.
    Beispiel 2: GUI-Thread erstellt einen weiteren Thread, nennen wir ihn SinnlosThread. SinnlosThread erstellt Instanz von Arbeiter-Thread. Event-Empfangende Klasse ist kein Control. Event wird mittels SinnlosThread gefangen. Kein Zugriff auf GUI direkt möglich, da ich mich nicht im GUI-Thread befinde.



    Unterm Strich ist es das gleiche, oder? Oder hab ich da irgendwas nicht richtig verstanden?
    Also ich glaub, du hasts richtig verstanden. Wenn der Event-Empfänger kein Control ist, dann kriegt er in picoflops Variante auch kein Gui-verträgliches Event.

    Ich hätte nochne Variante: Als invokendes Control nimm einfach Application.OpenForms(0). Dann musst du auch nicht mit GetInvokationlist den EreignisDelegaten auseinander nehmen, und die Abonennten einzeln anrufen.

    Du kannst auch den AsyncWorker - CodeProject nehmen - da ist sogar ein Mechanismus drin, der absichert, obs MainForm inzwischen geschlossen wurde - die App also eigentlich bereits beendet ist.
    Der Artikel ist eh nützlich, um mit Threading vertraut zu machen.

    Außerdem stellt AsyncWorker die einfachste Syntax zur Verfügung, die zum Verlagern von Aufrufen in annere Threads denkbar ist.

    Für die Variante mittm AsyncOperationManager legen sich übrigens üblicherweise die - ich nenn sie mal - Architektur-Astronauten ins Zeug: "Weil man kann ja nie wissen, ob man eine Klasse immer nur in Windows.Forms verwenden wird." Sobald man die Klasse in eine WPF-Umgebung portiert (machen die ja ständig ;) ), sitzt man mittm Arsch auf Grundeis.
    Ah - die AsyncOperationManager-Implementation hat auch einen Mangel: Es ist doch deppert, für jedes Arbeiter-Objekt ein neues asyncOp - Dingsbums zu creiern und zu merken. Das hätten die auch Private Shared machen können, weil eines reicht für die ganze Anwendung.

    ErfinderDesRades schrieb:

    Also ich glaub, du hasts richtig verstanden. Wenn der Event-Empfänger kein Control ist, dann kriegt er in picoflops Variante auch kein Gui-verträgliches Event.

    Das ist korrekt. Wird als Target keine Control-Instanz verwendet, wird den Eventhandler vom SinnlosThread ausgeführt. Über das Control kann er feststellen, an welchen Thread er den Task marshallen muss. Die entscheidende Stelle ist diese Abfrage:

    VB.NET-Quellcode

    1. If TypeOf d.Target Is Control AndAlso DirectCast(d.Target, Control).InvokeRequired Then
    2. DirectCast(d.Target, Control).BeginInvoke(fooEvent)
    3. Else
    4. RaiseEvent foo()
    5. End If

    "AndAlso" bedeutet "AND mit lazy evaluation" und sagt hier, dass alles, was nicht Control ist, direkt aufgerufen wird (also im Kontext von SinnlosThread). Alles andere wird zu dem Thread gemarshallt, der das Control erstellt hat (=Control.BeginInvoke), es sei denn, wir sind schon im richtigen Thread (dann ist InvokeRequired = False).
    Gruß
    hal2000
    @ErfinderDesRades: Das Beispiel AsyncWorker - CodeProject hat es aber echt in sich... Hab mir das gerade mal ne Stunde angeschaut. Hab zwar auch einiges verstanden, aber dafür hab ich auch ein paar neue Fragezeichen im Kopf... :)

    Ich habe meine Klasse jetzt ein wenig meinen Bedürfnissen angepasst. Der nächste Schritt wäre dann die richtige Implementation. Könntet ihr evtl. einmal jetzt über die Veränderungen schauen und nachgucken, ob alles i.O. ist? (Projekt ist auch hier als Dateianhang angefügt)

    VB.NET-Quellcode

    1. Public Class Form1
    2. Dim _arbeiterList As New List(Of Arbeiter)
    3. Private Sub butCreateWorker_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles butCreateWorker.Click
    4. Dim meinArbeiter As New Arbeiter()
    5. _arbeiterList.Add(meinArbeiter)
    6. AddHandler meinArbeiter.ArbeiterBeginntMitArbeit, AddressOf meinArbeiter_ArbeiterBeginntMitArbeit
    7. AddHandler meinArbeiter.ZwischenergebnisEingetroffen, AddressOf meinArbeiter_ZwischenergebnisEingetroffen
    8. AddHandler meinArbeiter.ArbeiterArbeitlos, AddressOf meinArbeiter_ArbeiterArbeitlos
    9. End Sub
    10. Private Sub butStopWorker_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles butStopWorker.Click
    11. If _arbeiterList.Count > 0 Then
    12. _arbeiterList(0).CancelWork()
    13. _arbeiterList.RemoveAt(0)
    14. End If
    15. End Sub
    16. Private Sub meinArbeiter_ArbeiterBeginntMitArbeit(ByRef sender As Arbeiter)
    17. ListBox1.Items.Add("Objekt Nr. " & sender.objektID.ToString & " beginnt mit der Arbeit.")
    18. ListBox1.SelectedIndex = ListBox1.Items.Count - 1 'Nach unten scrollen
    19. End Sub
    20. Private Sub meinArbeiter_ZwischenergebnisEingetroffen(ByRef sender As Arbeiter, ByVal ergebnis As String)
    21. ListBox1.Items.Add("Objekt Nr. " & sender.objektID.ToString & " hat Event gefeuert bei " & ergebnis.ToString & ".")
    22. ListBox1.SelectedIndex = ListBox1.Items.Count - 1 'Nach unten scrollen
    23. End Sub
    24. Private Sub meinArbeiter_ArbeiterArbeitlos(ByRef sender As Arbeiter, ByVal letztesErgenis As Integer)
    25. ListBox1.Items.Add("Objekt Nr. " & sender.objektID.ToString & " wurde unterbrochen. Letztes Ergebnis: " & letztesErgenis.ToString & ".")
    26. ListBox1.SelectedIndex = ListBox1.Items.Count - 1 'Nach unten scrollen
    27. End Sub
    28. Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
    29. For Each einArbeiter As Arbeiter In _arbeiterList
    30. einArbeiter.CancelWork()
    31. Next
    32. End Sub
    33. End Class

    VB.NET-Quellcode

    1. Public Class Arbeiter
    2. Private asyncOp As System.ComponentModel.AsyncOperation
    3. Public Event ArbeiterBeginntMitArbeit(ByRef sender As Arbeiter)
    4. Public Event ZwischenergebnisEingetroffen(ByRef sender As Arbeiter, ByVal ergebnis As Integer)
    5. Public Event ArbeiterArbeitlos(ByRef sender As Arbeiter, ByVal letztesErgenis As Integer)
    6. Private Shared erstellteObjekte As Integer 'Zählung aller erstellten Instanzen
    7. Public Property objektID As Integer 'ObjektID der aktuellen Instanz
    8. Private _canceled As Boolean
    9. Public Sub New()
    10. objektID = erstellteObjekte 'Dieser Instanz eine ID geben
    11. erstellteObjekte += 1 'Den Instanz-Counter erhöhen
    12. Dim workerThread As New Threading.Thread(AddressOf Rechne)
    13. workerThread.Start()
    14. End Sub
    15. Public Sub CancelWork()
    16. _canceled = True
    17. End Sub
    18. Public Sub Rechne()
    19. Dim i As Integer
    20. RaiseEventHelper(ArbeiterBeginntMitArbeitEvent, New Object() {Me})
    21. Do While i < 2147483646 'Fake-Rechen-Schleife. Dieser Code wird durch eine richtige Rechnung ersetzt.
    22. If _canceled Then Exit Do
    23. i += 1
    24. Threading.Thread.Sleep(1000)
    25. RaiseEventHelper(ZwischenergebnisEingetroffenEvent, New Object() {Me, i}) 'Event ZwischenergenisEingetroffen feuern
    26. Loop
    27. RaiseEventHelper(ArbeiterArbeitlosEvent, New Object() {Me, i}) 'Event ArbeiterArbeitlos feuern
    28. End Sub
    29. Public Sub RaiseEventHelper(ByVal eventDelegate As [Delegate], ByRef args() As Object)
    30. If eventDelegate Is Nothing Then Exit Sub
    31. For Each d As [Delegate] In eventDelegate.GetInvocationList
    32. If TypeOf d.Target Is Control Then
    33. If d Is Nothing Then Exit Sub
    34. Try
    35. If DirectCast(d.Target, Control).InvokeRequired Then
    36. DirectCast(d.Target, Control).Invoke(eventDelegate, args)
    37. Else
    38. eventDelegate.DynamicInvoke(args)
    39. End If
    40. Catch ex As Exception
    41. If Debugger.IsAttached Then Throw ex 'Nur um zu schauen, ob jemals eine Exception auftreten könnte...
    42. End Try
    43. End If
    44. Next
    45. End Sub
    46. End Class

    (1) Mache ich das mit "eventDelegate.DynamicInvoke(args)" richtig? Wüsste sonst nicht, wie ich das Event auf diese Art und Weise aufrufe...
    (2) Muss man Threads eigentlich irgendwie "disposen"? Oder reicht es, den Thread einfach "auslaufen" zu lassen und Verweise auf nothing zu setzen?

    (3) Sonst irgendwelche Verbesserungsvorschläge?
    Dateien
    • ThreadTestV3.zip

      (12,42 kB, 159 mal heruntergeladen, zuletzt: )

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

    habichmal eben den AsyncWorker-Helper-Projekt drangemacht - ich finde: code sieht schon stückweit aufgeräumter aus.

    Aber deine Events verstoßen ungefähr gegen jede DesignRegel, die Microsoft sich zum Thema hat einfallen lassen.
    Unglücklicherweise (für dich) habich grad ein Tutorial zum Thema verfasst:Tutorial zu Events
    Dateien
    • ThreadTest.zip

      (29,1 kB, 164 mal heruntergeladen, zuletzt: )

    ErfinderDesRades schrieb:

    habichmal eben den AsyncWorker-Helper-Projekt drangemacht - ich finde: code sieht schon stückweit aufgeräumter aus.

    Thx "ErfinderDesRades" für alles bisher! :) Ist definitiv eine Interessante Sache. Ich hab's vorerst nur überflogen und verstehe die Zusammenhänge noch nicht 100%. Ich werde aber wohl bei meiner Lösung (mit der Hilfe von Picoflop) bleiben. Ich sehe keine Nachteile darin und muss nur die Methode "RaiseEventHelper" meiner Klasse hinzufügen.

    ErfinderDesRades schrieb:

    Aber deine Events verstoßen ungefähr gegen jede DesignRegel, die Microsoft sich zum Thema hat einfallen lassen.

    Du meinst die Übergabeparameter "(ByVal Sender As Object, ByVal e As EventArgs)", richtig? Das verstand ich noch nie, warum Microsoft das so haben will... Meist ist nämlich nach jedem Event die erste Codezeile: "Dim meineTextbox as Textbox = DirectCast(Sender, Textbox). Und ob ich jetzt über "e.zwischenergebnis" auf mein Ergebnis zugreife, oder direkt über "zwischenergenis". Who cares? Spare ich sogar noch ne Menge Zeilen Code, weil ich die EventArgs-Klasse nicht erst noch erben und anpassen muss.. Ich glaub aber auch nicht, dass Microsoft diesen Standard umsonst einführt hat. Ich werd nur noch nicht dahinter gekommen sein, warum. :) Werde mir dein Tutorial mal durchlesen (habs grad auch nur überflogen). Vielleicht verstehe ich dann, welche Vorteile das haben soll. :) Bis dahin bleibe ich bei beim alten. :)

    Meine zwei kleinen Fragen sind aber leider noch offen:
    (1) Mache ich das mit "eventDelegate.DynamicInvoke(args)" richtig? Wüsste sonst nicht, wie ich das Event auf diese Art und Weise aufrufe...
    (2) Muss man Threads eigentlich irgendwie "disposen"? Oder reicht es, den Thread einfach "auslaufen" zu lassen und Verweise auf nothing zu setzen?

    Enixus schrieb:

    Vielleicht verstehe ich dann, welche Vorteile das haben soll.
    hihi. da wird das Tut auch nicht helfen - weilich weiß auch keinen ganz zwingenden Grund dafür.
    Also hat schon Vorteile, auch die Einheitlichkeit.
    Aber ich könnte mir auch Richtlinien vorstellen, die zu einem weniger umständlichen Design führten. Könnte man lange drüber diskutieren.

    Aber du verwendest auch byRef, wo's überhaupt nicht nötig ist, und an einer Stelle hattest du Strings gemacht, wos um zahlen ging (gugge nochma Option Strict On!)

    Und (1) sollte mit meinem Codesample doch beantwortet sein - also jdfs. ist das meine Antwort - also gugge doch, wie ichs da mach.

    zu (2): ja - Threads soll man möglichst sich selbst beenden lassen.
    Ganz einfach man kann Einflüsse auf das Event nehmen, welche nicht unbedingt logisch wären direkt in der Klasse unterzubringen, z.B. Cancel... Oder wo nicht eindeutig wären für welches Event das nun gilt...
    Außerdem ist der Gültigkeitsbereich evtl. manchmal von Vorteil...
    Zusätzlich fällt mir noch ein, dass z.B. über ein Event empfangene Daten beim nächsten Event wieder weg sind, wenn die Events nun Asynchron aufgerufen werden, dann werden die Variablen überschrieben die man noch benötigt, bzw. man müsste diese Sperren, bei mehreren Zugriffen auf einmal, was alles Resourcen kosten würde...
    Gut Manchmal nicht unbedingt an jeder Stelle nötig, aber meiner Meinung nach geschickt, außerdem machts ja nicht nur Microsoft so ;)
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Hey.

    Tut mir leid, dass ich diesen Thread ausgrabe, jedoch stehe ich vor einem kleinen Problem mit picoflops Code.
    Ein paar Sachen kommen mir dabei komisch vor... wäre nett, wenn mich jemand darüber aufklären könnte.
    Nähere Beschreibung als Kommentare im Code:

    VB.NET-Quellcode

    1. Public Class EventRaiser
    2. Public Event foo()
    3. Private Sub RaiseMyEventRight()
    4. For Each d As [Delegate] In fooEvent.GetInvocationList
    5. If TypeOf d.Target Is Control AndAlso DirectCast(d.Target, Control).InvokeRequired Then
    6. DirectCast(d.Target, Control).BeginInvoke(fooEvent) ' warum fooEvent? Müsste hier nicht d hin? Wenn nein, wieso nicht?
    7. Else
    8. RaiseEvent foo() ' wenn die For-Each-Schleife mehrmals durchläuft und niemals ein Invoke benötigt wird, wird das Event dann nicht für alle, die es abonniert haben, mehrmals aufgerufen?
    9. End If
    10. Next
    11. End Sub
    12. Public Sub DoIt()
    13. Dim t As New Threading.Thread(AddressOf RaiseMyEventRight)
    14. t.Start()
    15. End Sub
    16. End Class


    Danke im Voraus!
    jo, ich glaub du hast recht. Also der Ansatz leuchtet mir ein, dass der Multicast-Delegat in einzelne Delegaten zerteilt wird, und für jeden einzelnen wird bei Aufrufen, die aus einer Control-Klasse erfolgten auf InvokeRequired getestet.
    Umständlich, und nicht immer sicher, aber doch weitgehend.

    Aber wies umgesetzt ist, scheint mirs jetzt auch net richtig funzen zu können.
    Jo.
    Hab's jetzt so gelöst (C#):

    C-Quellcode

    1. EventHandler<SearchResultEventArgs> handler = KeywordFound;
    2. foreach (Delegate del in handler.GetInvocationList())
    3. {
    4. if (del.Target is System.Windows.Forms.Control && ((System.Windows.Forms.Control)del.Target).InvokeRequired)
    5. ((System.Windows.Forms.Control)del.Target).BeginInvoke(del, sender, e); // sollte man hier del vielleicht noch casten, wie unten?
    6. else
    7. ((EventHandler<SearchResultEventArgs>)del)(sender, e);
    8. }
    Scheint zu funktionieren.

    Hier der selbe Code in VB.NET:

    VB.NET-Quellcode

    1. For Each del As [Delegate] In KeywordFoundEvent.GetInvocationList()
    2. If TypeOf del.Target Is System.Windows.Forms.Control AndAlso DirectCast(del.Target, System.Windows.Forms.Control).InvokeRequired Then
    3. DirectCast(del.Target, System.Windows.Forms.Control).BeginInvoke(del, sender, e)
    4. Else
    5. DirectCast(del, EventHandler(Of SearchResultEventArgs))(sender, e)
    6. End If
    7. Next
    Für Verbesserungsvorschläge bin ich natürlich offen. :P
    naja, ohne jedes Heckmeck:

    VB.NET-Quellcode

    1. Application.OpenForms(0).BeginInvoke(new EventHandler(Of SearchResultEventArgs)( _
    2. Sub (e) RaiseEvent KeywordFound(me, e))

    offensichtlich handelts sich um eine asynchron auszuführende Suche-Klasse, und da braucht man nicht groß Delegaten zu zerpflücken, und auf InvokeRequired testen und Zeugs, sondern, das Invoking notwendig ist, ist doch bekannt.

    Eher müssteman auf InvokeRequired testen, und wenns nicht Required ist, eine Exception werfen, denn dann wird die Suche falsch aufgerufen.