Event aus Thread starten, Invoke nötig

  • VB.NET

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

    Event aus Thread starten, Invoke nötig

    Hallu,

    ich mach mir grad ne eigene Downloadklasse (via Stream) und bin gerade auf folgendes Problem gestoßen:

    Der Download findet in einem Thread statt und mit jedem Byte, der gespeichert wird, wird ein Event aufgerufen. Dieses behandle ich in meiner "Form1" und krieg diesen Fehler:

    Quellcode

    1. Zusätzliche Informationen: Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement ProgressBar1 erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.


    Was das bedeutet ist mir klar, allerdings möchte ich Invoke() ungern beim behandeln des Events (in Form1) verwenden sondern "irgendwie" im Thread.


    Liebe Grüße,

    -Tim

    C#-Quellcode

    1. this.Invoke((MethodInvoker)delegate {
    2. progressBar1.Value = 23;
    3. });


    VB.NET-Quellcode

    1. Me.Invoke(DirectCast(Sub() progressBar1.Value = 23, MethodInvoker))

    "Nichts ist unendlich, bis auf die menschliche Dummheit" - Albert Einstein
    "Man sollte nicht alles vertrauen, was im Netz steht" - Abraham Lincoln
    Dann solltest du dir vielleicht mal überlegen, ob Async-Methoden nicht vielleicht doch der richtige Weg ist.
    Die brauchen jedoch .NET 4.5+, d.h. keiner ohne Windows 7 (oder höher) kann es benutzen. Oder guck mal, ob du Microsoft Async verwenden kannst. Das ist ein NuGet-Paket

    EDIT:
    Hier sind einige Beispiele mit Async (ist C#). Ich kann dir nur raten, solltest du dir den Code anschauen, dass du CTRL+F und dann async machst, damit alle Async-Methoden hervorgehoben werden.
    "Nichts ist unendlich, bis auf die menschliche Dummheit" - Albert Einstein
    "Man sollte nicht alles vertrauen, was im Netz steht" - Abraham Lincoln
    Async-await kann auch schon in 4.0 verwendet werden, wenn man NuGet benutzt.
    #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 :!:
    @Trade Hab ich doch geschrieben ;)

    BeatsleigherXDA schrieb:

    Oder guck mal, ob du Microsoft Async verwenden kannst. Das ist ein NuGet-Paket
    "Nichts ist unendlich, bis auf die menschliche Dummheit" - Albert Einstein
    "Man sollte nicht alles vertrauen, was im Netz steht" - Abraham Lincoln
    @Artentus

    backtothetoast schrieb:

    ich hätte es halt nur mit "Invoke(Sub() Methode())" gemacht.


    backtothetoast schrieb:

    in einer externen Klasse und für den gibt es kein Invoke, ist ja auch keine "Form".
    "Nichts ist unendlich, bis auf die menschliche Dummheit" - Albert Einstein
    "Man sollte nicht alles vertrauen, was im Netz steht" - Abraham Lincoln
    Die Klasse hat sich auch nicht ums Invoking zu kümmern, das Event wird einfach im anderen Thread gefeuert. Daran ändert sich auch nichts, wenn die Klasse in eine DLL kommt.
    Ums Invoking hat sich ganz alleine der Aufrufer zu kümmern, falls es denn notwendig ist. Wenn du also was in WindowsForms anzeigen willst, wird das Invoking im Eventhandler der Form/des Controls durchgeführt.
    1) Das Problem wird sein, dass der Thread weiterlaufen soll, und nur ein Updating im Gui delegieren. Ich kenn mich mit Async nicht aus, aber befürchte, dasses solch nicht zu leisten imstande ist.
    2) @TE: Verstehe ich richtig, dass du für jedes besch... Byte, was du downloadest, die Progressbar updaten willst?
    3) Wenn du mir zeigst, wie du das Event aufrufst, kann ich das umbauen, dasses im MainThread ausgelöst wird.
    Oder versuch selber, dem Trick auf die Schliche zu kommen: Auf Datagridvie aus externen Thread zugraifen - da hab ich das auch verbaut.
    Probierma so:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Option Strict On
    2. Imports System
    3. Imports System.Net
    4. Imports System.IO
    5. Class Downloader
    6. Private DownloadURL As String
    7. Private TargetPath As String
    8. Public Event DownloadFileProgressChanged(ByVal sender As Object, ByVal e As DownloaderEventArgs.DownloadFileProgressChanged)
    9. Public Event DownloadFileCompleted(ByVal sender As Object, ByVal e As DownloaderEventArgs.DownloadFileCompleted)
    10. Public Event DownloadFileFailed(ByVal sender As Object, ByVal e As DownloaderEventArgs.DownloadFileFailed)
    11. Private OnDownloadFileProgressChanged As New Action(Of DownloaderEventArgs.DownloadFileProgressChanged)(Sub(e) RaiseEvent DownloadFileProgressChanged(Me, e))
    12. Private OnDownloadFileCompleted As New Action(Of DownloaderEventArgs.DownloadFileCompleted)(Sub(e) RaiseEvent DownloadFileCompleted(Me, e))
    13. Private OnDownloadFileFailed As New Action(Of DownloaderEventArgs.DownloadFileFailed)(Sub(e) RaiseEvent DownloadFileFailed(Me, e))
    14. Sub New()
    15. MyBase.New()
    16. End Sub
    17. Public Sub DownloadFile(ByVal _DownloadURL As String, ByVal _TargetPath As String)
    18. DownloadURL = _DownloadURL
    19. TargetPath = _TargetPath
    20. Dim dlt As New Threading.Thread(AddressOf DownloadThread)
    21. dlt.Start()
    22. End Sub
    23. Private Sub DownloadThread()
    24. If UrlTester.Test(DownloadURL) = False Then
    25. Dim dff As New DownloaderEventArgs.DownloadFileFailed
    26. Application.OpenForms(0).BeginInvoke(OnDownloadFileFailed, dff)
    27. Exit Sub ' clean enough?
    28. End If
    29. Dim webRequest As WebRequest = webRequest.Create(DownloadURL)
    30. Dim webResponse As WebResponse = webRequest.GetResponse()
    31. Dim Stream As Stream = webResponse.GetResponseStream()
    32. Dim BinaryReader As New BinaryReader(Stream)
    33. Dim FileStream As New FileStream(TargetPath, FileMode.Create)
    34. For i = 0 To webResponse.ContentLength - 1
    35. FileStream.WriteByte(BinaryReader.ReadByte())
    36. Dim dpc As New DownloaderEventArgs.DownloadFileProgressChanged
    37. dpc.TotalBytesToReceive = webResponse.ContentLength
    38. dpc.BytesReceived = FileStream.Length
    39. dpc.ProgressPercentage = CInt(Math.Round(dpc.BytesReceived / dpc.TotalBytesToReceive * 100))
    40. Application.OpenForms(0).BeginInvoke(OnDownloadFileProgressChanged, dpc)
    41. Next
    42. FileStream.Flush()
    43. FileStream.Close()
    44. Dim dfc As New DownloaderEventArgs.DownloadFileCompleted
    45. Application.OpenForms(0).BeginInvoke(OnDownloadFileCompleted, dfc)
    46. End Sub
    47. End Class
    48. Class DownloaderEventArgs
    49. Structure DownloadFileProgressChanged
    50. Dim TotalBytesToReceive As Long
    51. Dim BytesReceived As Long
    52. Dim ProgressPercentage As Integer
    53. End Structure
    54. Structure DownloadFileCompleted
    55. End Structure
    56. Structure DownloadFileFailed
    57. End Structure
    58. End Class
    59. Module UrlTester
    60. Function Test(ByVal URL As String) As Boolean
    61. Try
    62. WebRequest.Create(URL)
    63. Return True
    64. Catch ex As Exception
    65. Return False
    66. End Try
    67. End Function
    68. End Module
    Allerdings deine EventArgs spotten jeder Beschreibung. Ich täte empfehlen, du hälst dich da an die Konventionen, zumal das ja eine Dll werden soll.
    gugge Alles über Events

    achso - wie ist denn dein Plan, um die Performance nicht zu versauen?

    Ich hab nämlich vor Zeiten mal eine Komplett-Lösung zum Thema AsyncWorker gecodet, die auch zum PerformanceProblem was hat:
    codeproject.com/Articles/60382…ackgroundWorker-and-about
    Also ich bleibe ja dabei, das Invoking hat an dieser Stelle gar nicht zu erfolgen.
    Application.OpenForms(0) ist ein Workaround, der dadurch funktioniert, dass man etwas aunutzt, was dafür eigentlich nicht gedacht war. In WinForms kann man das gerade noch durchgehen lassen, obwohl es auch schöner wie oben beschrieben gelöst ist, aber was wenn die DLL in ner WPF-Anwendung verwendet wird? Oder in ner Console? Oder in einem Dienst? Es lässt sich nicht voraussehen, in welchem Kontext man sich befindet, und deshalb gibt es auch keine Pauschallösung, wie Threadübergreifende Vorgänge zu handhaben sind. Aus diesem Grund ist alleine der Aufrufer verantwortlich dafür, denn nur der kann es wissen.
    Ja, diese Argumentation kenne ich, da hab ich mich schon mit Kängaruh und picoflop herumgefetzt.
    Mein Standpunkt ist, dass diese Dinge immer und immer wieder dieselben sind, und deshalb gehört da etwas wiederverwendbares gecodet - etwa den Asyncworker.
    Zumal die Problematik ziemlich vertrackt ist - einmal das Performance-Problem, wenn man Gefahr läuft, viel häufiger zu updaten als der User ühaupt gucken kann.
    Zum andern aber auch das Problem, dass die Anwendung geschlossen werden kann, während der Nebenthread noch läuft - das macht nämlich auch schön Peng.
    Und auch da ist Openforms nützlich, denn wenn diese Auflistung leer ist, sollte man nicht mehr updaten, sondern den Thread auch schleunigst beenden.

    Für Wpf hab ich den übrigens auf SynchronisizeContext umgestellt, mit dem kleinen Nachteil, dass man da das Teil explizit initialisieren muss.

    Ist auch sicher gut möglich, dasses Szenarien gibt, wo man damit auf Nase fällt.
    Aber jedes jedesmal dieselben Probleme immer wieder neu lösen ist total umständlich, und ist auch fehleranfällig und schlecht wartbar.

    Und zu allem Überfluss ist der Asyncworker ja nicht die einzige Klasse auf der Welt, die sich erfrecht, nur in WinForms zu funzen, und ihre Events grundsätzlich im MainThread zu feuern.
    Backgroundworker und Winforms.Timer tun ja ebenso, und da regt sich keiner drüber auf.
    Wenn man die Funktionalitäten auf WinForms beschränken möchte...
    Allerdings kann ich dir nicht wirklich zustimmen, dass es dadurch später komplizierter wird. In WinForms schreib ich halt diese eine Zeile mehr hin, ist reine Routine und man erkennt auch schön, was passiert. In WPF brauchts mich gar nicht zu kümmern, da übernimmt das das GUI-System nämlich sowieso schon, so wies sein soll.
    ja vlt. haste recht - wenn du das in einer Zeile gelöst bekommst - ist natürlich auch nicht schlecht.

    Ich finde das immer sehr unschön, dieses auf InvokeRequired zu testen und sich dann nochmal selbst aufrufen - wo dann nochmal auf InvokeRequired getestet wird, um dann endlich was zu tun.
    Und das alles, wo man doch von vornherein weiss, das die Aufrufe im Nebenthread einlaufen.