无法从非主线程隐藏WinForm窗体的技术问题求助
主线程包含FrmMain和FrmWait两个窗体,另有一个执行耗时任务的工作线程。FrmWait仅显示带“Downloading”动态GIF的PictureBox,希望在工作线程完成任务(或触发异常时)隐藏它,但尝试Forms/Controls的跨线程调用方式均无效。
现有代码
FrmMain 代码
Private Sub StartThread() Debug.Print("Main ThreadId is: " & System.Environment.CurrentManagedThreadId) FrmWait.Show() Dim trd As New Thread(Sub() Flag.Agisci(Num, Value1, value2) End Sub) trd.Start() End Sub Private Sub FrmMain_HandleCreated(sender As Object, e As EventArgs) Handles Me.HandleCreated Debug.Print("FrmMain Handle has been created: " & Me.Handle.ToString) End Sub
FrmWait 代码
Private Delegate Sub CloseForm(hide As Boolean) Public Sub CloseWait(hide As Boolean) Debug.Print("******************* Entring in CloseWait") Try Dim h As IntPtr = Me.Handle Debug.Print("................... InvokeRequired is: " & Me.InvokeRequired.ToString & " on Handle: " & h.ToString) If Me.InvokeRequired Then Debug.Print("----------- Invoke is Required !!") Me.Invoke(New CloseForm(AddressOf CloseWait), hide) Else Debug.Print("+++++++++++ Calling Me.Visible = " & hide.ToString) Me.Visible = hide End If Catch ex As Exception Debug.Print(ex.ToString) End Try End Sub Private Sub FrmWait_HandleCreated(sender As Object, e As EventArgs) Handles Me.HandleCreated Debug.Print("FrmWait Handle has been created: : " & Me.Handle.ToString) End Sub
工作线程类(Flag)代码
Public Sub Agisci(Num As Integer, Value1 As String, Value2 As String) Try Debug.Print("Task ThreadId is: " & System.Environment.CurrentManagedThreadId) Sleep(2000) Throw New Exception("Foo") 'some stuff bypassed by the exception Catch ex As Exception Debug.Print(ex.Message) FrmWait.CloseWait(True) 'this is another Form that is correctly displayed FrmMessBox.SetText(ex.Message) FrmMessBox.ShowDialog() End Try End Sub
输出日志
FrmMain Handle has been created: 4785876
Main ThreadId is: 1
FrmWait Handle has been created: 655546
Task ThreadId is: 5
发生异常: 'System.Exception' in MyApp.exe -Foo
******************* Entring in CloseWait
FrmWait Handle has been created: 917984
................... InvokeRequired is: False on Handle: 917984 '****** 为什么是FALSE?????
+++++++++++ Calling Me.Visible = False '************* 无效!
问题根源
日志里的关键线索是FrmWait出现了两个不同的句柄(655546和917984):
- 主线程调用
FrmWait.Show()时,创建的是第一个句柄(属于主线程的窗体实例) - 工作线程调用
FrmWait.CloseWait()时,因为VB的默认窗体实例化机制(直接用类名访问会自动创建新实例),这里实际操作的是全新的FrmWait实例,而非主线程显示的那个。
这个新实例的句柄是在工作线程中创建的,所以InvokeRequired返回False,但修改它的Visible属性对主线程的窗体完全没有影响。
解决方法
方法1:显式传递FrmWait实例到工作线程
这是最可靠的方式,确保操作的是同一个窗体:
- 修改Flag类的
Agisci方法,接收FrmWait实例作为参数:
Public Sub Agisci(Num As Integer, Value1 As String, Value2 As String, waitForm As FrmWait) Try Debug.Print("Task ThreadId is: " & System.Environment.CurrentManagedThreadId) Sleep(2000) Throw New Exception("Foo") 'some stuff bypassed by the exception Catch ex As Exception Debug.Print(ex.Message) waitForm.CloseWait(False) '传False对应隐藏窗体,原代码传True是设置Visible=True,和需求相反 FrmMessBox.SetText(ex.Message) FrmMessBox.ShowDialog() End Try End Sub
- 修改FrmMain的
StartThread方法,传递FrmWait实例:
Private Sub StartThread() Debug.Print("Main ThreadId is: " & System.Environment.CurrentManagedThreadId) Dim waitForm As New FrmWait() waitForm.Show() Dim trd As New Thread(Sub() Flag.Agisci(Num, Value1, value2, waitForm) End Sub) trd.Start() End Sub
方法2:使用主线程同步上下文
如果必须保留类名访问的方式,可以通过主线程同步上下文执行操作:
- 在FrmMain中定义同步上下文变量:
Private mainSyncContext As SynchronizationContext
- 在FrmMain的Load事件中初始化:
Private Sub FrmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load mainSyncContext = SynchronizationContext.Current End Sub
- 修改FrmWait的
CloseWait方法:
Public Sub CloseWait(hide As Boolean) mainSyncContext.Post(Sub() Me.Visible = Not hide '根据需求调整,隐藏则设为False End Sub, Nothing) End Sub
额外注意点
- 原代码中
CloseWait(True)是设置Me.Visible = True,和“隐藏窗体”的需求相反,需要改为CloseWait(False)或者调整方法内的逻辑。 - 避免直接用窗体类名访问实例,显式创建并传递实例能彻底避免VB自动创建新实例的坑。
内容的提问来源于stack exchange,提问作者ezio

