VB.NET: Threadübergreifender Vorgang: Threads, Invoke und Events

Mo, 23.03.2009 - 14:59 -- Daniel Espendiller

Mit Threads können in vb.net bestimmte Operationen von der Anwendung "abgelöst" werden. Mithilfe der Auslagerungen können z.B Timer oder Rechnenvorgängen im Hintergrund ausgeführt werden.
Möchte man aus einem Fremden Thread allerdings das Ergebnis später in der Anwendung wieder verwenden oder einen Event auslösen, so wird dieses penitrant von vb.net verweigert, da hier eine strikte Trennung zwischen den Vorgängen vorgegeben ist. Diese müssen vorher in einen "Invoke" ausgelagert werden, damit dort keine komplikationen auftreten können.
Wird z.B. ein Wert in eine Textbox geschrieben, so muss sicher gestellt werden, dass die Textbox gerade zur Verfügung steht und nicht bereits von irgendeinem anderen Thread bearbeitet wird. Sicherlich wird dieses bei einer simplen Textbox in einfachen Anwendungen selten passieren, man sollte dieses allerdings nicht unterschätzen. Wenn man sich einfach eingearbeitet hat, macht dieses später sogar Sinn.

Entsprechende Meldungen:

Ungültiger threadübergreifender Vorgang: Der Zugriff auf
das Steuerelement [...] erfolgte von einem anderen Thread
aus als dem Thread, für den es erstellt wurde. 

Ich habe länger eine wirklich 100%ige Methode gesucht bis ich schließlich auf codeproject.com fündig geworden bin. Somit habe ich den vorhanden Quelltext genommen, in eine Klasse verpackt, deren Ergebnis unten zu sehen ist.
Herzstück ist die Funktion OnEvent, welche die Invoke Methode zur Verfügung steht. Hier wird einfach eine Klasse (ThreadEventEventArgs) übergeben, die alle Daten für einen Event bereit hält. Ausgelöst wird das Event schließlich über Ticker(eventMsg), welches z.B in einer Form entgegen genommen werden kann und ohne Begrenzung weiterverarbeitet werden kann.

Form1.vb

Public Class Form1
    Dim WithEvents ExampleTicker As New ThreadEvent
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        ExampleTicker.Intervall = 1
        ExampleTicker.start()
    End Sub
    Private Sub ExampleTicker_received(ByVal sender As Object, ByVal e As ThreadEvent.ThreadEventEventArgs) Handles ExampleTicker.received
        TextBox1.Text = e.ReceivedText
    End Sub
End Class

ThreadEvent.vb

Imports System.ComponentModel
 
Public Class ThreadEvent
    Public Event received As EventHandler(Of ThreadEventEventArgs)
    Private _Intervall As Integer = 1
    Dim ThreadReceive As System.Threading.Thread
    Dim _stopped As Boolean = False
    Public Property Intervall() As Integer
        Get
            Return _Intervall
        End Get
        Set(ByVal value As Integer)
            _Intervall = value
        End Set
    End Property
    Private Sub ReceiveMessages()
        Dim i As Integer = 0
        Do
            i = i + 1
            Dim eventMsg As New ThreadEventEventArgs
            eventMsg.ReceivedText = i.ToString
            Ticker(eventMsg)
            Threading.Thread.Sleep(Me._Intervall * 1000)
        Loop Until _stopped = True
    End Sub
    Sub start()
        NewInitialize()
    End Sub
    Sub stopped()
        _stopped = True
        If Not ThreadReceive Is Nothing Then
            If ThreadReceive.IsAlive Then
                ThreadReceive.Abort()
            End If
        End If
    End Sub
    Private Sub NewInitialize()
        ThreadReceive = New System.Threading.Thread(AddressOf ReceiveMessages)
        ThreadReceive.Start()
    End Sub
    Protected Sub Ticker(ByVal e As ThreadEventEventArgs)
        OnEvent(Me, CType(e, EventArgs), receivedEvent)
    End Sub
    Private Sub OnEvent(ByVal sender As Object, ByVal e As EventArgs, ByVal [event] As System.Delegate)
        Dim eventFired As Boolean = False
        If [event] IsNot Nothing Then
            For Each singleCast As System.Delegate In [event].GetInvocationList()
                eventFired = False
                Try
                    Dim syncInvoke As ISynchronizeInvoke = CType(singleCast.Target, ISynchronizeInvoke)
                    If syncInvoke IsNot Nothing AndAlso syncInvoke.InvokeRequired Then
                        eventFired = True
                        syncInvoke.Invoke(singleCast, New Object() {sender, e})
                    Else
                        eventFired = True
                        singleCast.DynamicInvoke(New Object() {sender, e})
                    End If
                Catch ex As System.Reflection.TargetInvocationException
                    If Not eventFired Then
                        singleCast.DynamicInvoke(New Object() {sender, e})
                    End If
                End Try
            Next
        End If
    End Sub
    Public Class ThreadEventEventArgs
        Inherits EventArgs
        Dim mReceivedText As String
        Dim mReceivedRawText As String
        Public Property ReceivedText() As String
            Get
                Return mReceivedText
            End Get
            Set(ByVal Value As String)
                mReceivedText = Value
            End Set
        End Property
        Public Property ReceivedRawText() As String
            Get
                Return mReceivedRawText
            End Get
            Set(ByVal Value As String)
                mReceivedRawText = Value
            End Set
        End Property
    End Class
 
End Class

Disqus - noscript

Danke, habe lange nach so einer Lösung gesucht und nun endlich gefunden.
Danke, du bist mein persönlicher Held! auch wenn der Post schon 2 Jahre auf dem Buckl hat, aber das is genau das, was ich gesucht hab ;)
Daraus konnt ich mir ne Klasse baun die genau das macht, was ich haben wollt! ;)