1

I am using the SQLDependency class to listen for SQL notifications and the SQLDependency.OnChange event is executed on a different thread from the UI thread. My intention is to update the data in a grid view when this event is fired so SQL server database changes can instantly be reflected in the grid.

If I run my LoadData method to reload the data into the grid I get a cross-threading error:

enter image description here

I have created a custom class to hold all the SQLDependency related code, and there is a single instance of this class declared globally across my application. I have an event on the class that is raised inside the SQLDependency.OnChanged event. This event is then handled on various different forms so their data can be reloaded. However, because the OnChange event is raised on a different thread, I need to add logic to run first:

Delegate Sub ReloadCallback()
Private Sub LoadOnUI()
    If Me.InvokeRequired Then
        Dim d As ReloadCallback = New ReloadCallback(AddressOf LoadOnUI)
        Me.Invoke(d)
    Else
        Invoke(New MethodInvoker(Sub() RefreshData()))
    End If
End Sub

I would prefer to avoid having to duplicate this code and copy it to each for where my custom event is handled. This answer is pretty much exactly what I am looking for but I can't figure out how to implement the code provided.

Logic down the lines of, get the invocation list of the event and get the thread of one of the handlers, then raise the event on that thread. With my limited experience I am unable to write code to do this on my own. Any assistance would be appreciated.

Objective: Raise an event on the UI thread with no extra code repeated in the event handlers.

Solution: Run this code on the target thread ahead of time (e.g. class constructor).

objSyncContext = SynchronizationContext.Current

Call this method RunOnUIThread(AddressOf RefreshData) from another thread to run the referenced method on the target thread.

Delegate Sub CallDelegate()
Private Sub RunOnUIThread(objEvent As CallDelegate)
    If objSyncContext Is Nothing Then
        objEvent()
    Else
        objSyncContext.Post(Sub() objEvent(), Nothing)
    End If
End Sub
AwiringCameron
  • 620
  • 3
  • 18

1 Answers1

-1

Here's a quick example using SynchronizationContext.

Note that it assumes you are creating the class from the main UI thread, and you store that current context from the constructor:

Public Class Form1

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim d As New Duck("Bob")
        AddHandler d.Quack, AddressOf duck_Quack
        Label1.Text = "Waiting for Quack..."
        d.RaiseThreadedQuack(3)
    End Sub

    Private Sub duck_Quack(source As Duck)
        Label1.Text = "Quack received from: " & source.Name
    End Sub

End Class

Public Class Duck

    Public ReadOnly Property Name() As String
    Private sc As WindowsFormsSynchronizationContext

    Public Sub New(ByVal name As String)
        Me.Name = name
        sc = WindowsFormsSynchronizationContext.Current
    End Sub

    Public Event Quack(ByVal source As Duck)

    Public Sub RaiseThreadedQuack(ByVal delayInSeconds As Integer)
        Dim T As New Threading.Thread(AddressOf ThreadedQuack)
        T.Start(delayInSeconds)
    End Sub

    Private Sub ThreadedQuack(ByVal O As Object)
        System.Threading.Thread.Sleep(TimeSpan.FromSeconds(O).TotalMilliseconds)

        sc.Post(AddressOf RaiseUIQuack, Me)
    End Sub

    Private Sub RaiseUIQuack(ByVal O As Object)
        RaiseEvent Quack(O)
    End Sub

End Class
Idle_Mind
  • 38,363
  • 3
  • 29
  • 40