Visual Basic.NET:如何创建线程来更新 UI

Sid*_*Bob 4 vb.net multithreading message-pump ui-thread applicationcontext

处理计算量大的任务的常用 VB 方法是将其放入后台工作线程,而主线程继续处理 UI。

不管出于什么原因,我需要以相反的方式执行此操作:主线程执行繁重的工作,后台线程更新 UI。

这是我到目前为止所拥有的。唯一的问题是,虽然 UI 窗口 (Form1) 确实被重绘,但您无法与其交互,甚至无法移动它或调整其大小(鼠标光标变成沙漏状并且无法单击)。

Public Class ProgressDisplay

Private trd As Thread

    Public Sub New()
        trd = New Thread(AddressOf threadtask)
        trd.Start()
    End Sub

    Private Sub threadtask()
        Dim f1 As Form1
        f1 = New Form1
        f1.Show()
        Do
            f1.Update()
            Thread.Sleep(100)
        Loop
    End Sub

End Class
Run Code Online (Sandbox Code Playgroud)

编辑:理想情况下,我需要向客户端呈现这样的界面

Public Class ProgressDisplay
    Public Sub New()
    Public Sub Update(byval progress as int)
End Class
Run Code Online (Sandbox Code Playgroud)

客户端将像这样调用它(实际上是在 COM 上的非托管 c++ 中,但你明白了):

Dim prog = new ProgressDisplay()
DoLotsOfWork(addressof prog.update) ' DoLotsOfWork method takes a callback argument to keep client informed of progress
Run Code Online (Sandbox Code Playgroud)

J..*_*... 5

为了清楚地重述问题 - 您需要向将在自己的程序中使用它的客户提供一个可视化组件。客户端的程序在您的控制之外,会占用其主(即:UI)线程,并且您需要让可视组件在客户端的程序被冻结时继续工作。

让我们在这里明确一点——客户的程序冻结是他们的问题,这是他们的应用程序代码缺陷的直接结果。下面是一种黑客攻击,而且是一种可怕的黑客攻击,任何人都不应该使用。因此,虽然您可以执行以下操作,但它几乎不应该被视为任何事情的推荐解决方案。它与所有最佳实践背道而驰。

话虽如此,您需要创建第二个应用程序上下文和一个可以在主 UI 线程旁边继续运行的新消息循环。像这样的类会起作用:

Imports System.Threading

Public Class SecondUIClass

    Private appCtx As ApplicationContext
    Private formStep As Form
    Private trd As Thread
    Private pgBar As ProgressBar
    Delegate Sub dlgStepIt()

    Public Sub New()
        trd = New Thread(AddressOf NewUIThread)
        trd.SetApartmentState(ApartmentState.STA)
        trd.IsBackground = True
        trd.Start()
    End Sub

    Private Sub NewUIThread()
        formStep = New Form()
        pgBar = New ProgressBar()
        formStep.Controls.Add(pgBar)
        appCtx = New ApplicationContext(formStep)
        Application.Run(appCtx)
    End Sub

    Public Sub StepTheBar()
        formStep.Invoke(New dlgStepIt(AddressOf tStepIt))
    End Sub

    Private Sub tStepIt()
        pgBar.PerformStep()
    End Sub

End Class
Run Code Online (Sandbox Code Playgroud)

本质上,您对上述类所做的事情是在新的 STA 线程中创建新的应用程序上下文(为该线程提供消息循环)。该上下文包含一个主窗体(赋予线程所有权并负责其消息处理),该窗体可以在主 UI 线程之外继续运行。这很像程序中的程序 - 两个 UI 线程,每个线程都有自己的互斥控件集。

与新 UI 线程(或其窗体)拥有的任何控件交互的调用必须从Control.Invoke主 UI 线程(或其他线程)进行编组,以确保新 UI 线程是执行交互的线程。您也可以BeginInvoke在这里使用。

这个类没有清理代码,没有安全检查等(请注意),我什至不确定它会优雅地完成 - 我把这个任务留给你。这只是说明了一种入门方法。在主窗体中,您可以执行以下操作:

Public Class Form1

    Private pgClass As New SecondUIClass

    Private Sub Button1_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles Button1.Click
        Dim i As Integer
        For i = 1 To 10
            System.Threading.Thread.Sleep(1000)
            pgClass.StepTheBar()
        Next
    End Sub
End Class
Run Code Online (Sandbox Code Playgroud)

运行上面的应用程序将创建Form1由 . 创建的第二个表单pgClass。单击将在 Form1 执行循环时锁定它,但第二个窗体仍保持活动状态并响应,每次调用时更新其Button1进度栏。Form1Form1.StepTheBar()

实际上,在这种情况下,最好的解决方案是让“客户”学习如何正确编程,并从一开始就避免陷入这个难题。如果这是完全不可能的,并且您必须为他们创建一个组件,尽管其代码很差,但该组件仍将保持活动状态,那么上述方法可能是您唯一的手段。