UI 线程上的 .NET 引发事件

.NET raise event on UI thread

我正在使用 SQLDependency class to listen for SQL notifications and the SQLDependency.OnChange 事件在与 UI 线程不同的线程上执行。我的目的是在触发此事件时更新网格视图中的数据,以便 SQL 服务器数据库更改可以立即反映在网格中。

如果我 运行 我的 LoadData 方法将数据重新加载到网格中,我得到一个跨线程错误:

我创建了一个自定义 class 来保存所有 SQL 依赖关系相关的代码,并且在我的应用程序中全局声明了这个 class 的一个实例。我在 class 上有一个事件在 SQLDependency.OnChanged event. This event is then handled on various different forms so their data can be reloaded. However, because the OnChange 内部引发,事件是在另一个线程上引发的,我需要先向 运行 添加逻辑:

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

我宁愿避免复制此代码并将其复制到处理我的自定义事件的每个代码。 This answer 几乎正是我要找的东西,但我不知道如何实现所提供的代码。

按照以下逻辑行,获取事件的调用列表并获取其中一个处理程序的线程,然后在该线程上引发事件。以我有限的经验,我无法自己编写代码来完成这项工作。如有任何帮助,我们将不胜感激。

Objective:在 UI 线程上引发事件,事件处理程序中没有重复的额外代码。

解法: 运行 此代码提前在目标线程上(例如 class 构造函数)。

objSyncContext = SynchronizationContext.Current

从另一个线程调用此方法 RunOnUIThread(AddressOf RefreshData) 以 运行 目标线程上的引用方法。

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

这是一个使用 SynchronizationContext 的简单示例。

请注意,它假定您正在从主 UI 线程创建 class,并且您从构造函数存储当前上下文:

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