3

处理计算量大的任务的常用 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

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

Public Class ProgressDisplay
    Public Sub New()
    Public Sub Update(byval progress as int)
End Class

客户端会这样调用它(实际上是在非托管 C++ 中通过 COM,但你得到了图片):

Dim prog = new ProgressDisplay()
DoLotsOfWork(addressof prog.update) ' DoLotsOfWork method takes a callback argument to keep client informed of progress
4

3 回答 3

4

为了清楚地重申问题 - 您需要向将在他们自己的程序中使用它的客户提供一个可视化组件。客户端的程序,在您的控制之外,占用了它的主(即: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

本质上,您对上述类所做的是在新的 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

运行上面的应用程序将创建Form1以及由pgClass. 单击会Button1Form1Form1 通过其循环时锁定它,但第二个表单仍将保持活动状态并做出响应,每次Form1调用时都会更新其进度条.StepTheBar()

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

于 2012-06-22T12:43:05.877 回答
3

UI 只能通过创建它的线程进行更新。任何线程都可以请求调用 UI 线程上的方法,以便它可以通过使用该Control.Invoke方法更新 UI,但这只会等待 UI 线程不再忙。如果 UI 线程很忙,则任何人或任何人都无法更新 UI。Application.Run仅当主消息循环 (AKA ) 处理队列中的窗口消息并对其进行操作时,UI 才会更新。如果 UI 线程很忙、陷入循环或等待来自服务器的响应,它就无法处理那些窗口消息(除非你调用DoEvents,如果可能的话,我绝对不推荐)。所以,是的,当 UI 很忙时,它会被锁定。这就是为什么每个人都建议在单独的线程中执行任何重要的业务逻辑的全部原因。如果这不是问题,为什么会有人费心制作工作线程?

于 2012-06-22T12:00:47.080 回答
2

UI 不能从除主事件调度线程之外的任何其他线程进行更新。因此,您正在尝试做一些行不通的事情。

于 2012-06-22T11:54:50.730 回答