【问题标题】:vb.net problems with invokevb.net 调用问题
【发布时间】:2014-07-11 18:48:33
【问题描述】:

我有一个运行后台作业的线程,需要不时更新 GUI。我的程序被设计成当用户单击表单时,线程和后台操作仍然运行,但控件已被释放(用于内存管理目的)。

我一直在使用 Invoke() 和“If Control.Created = True”来确保线程可以成功更新控件而不会遇到任何异常。但是,当重新创建表单时,所有“Control.Created”值都为 false,并且 Invoke() 失败并显示“{“在创建窗口句柄之前无法在控件上调用 Invoke 或 BeginInvoke。”}”

我的猜测是,这与重新创建表单时分配了不同的句柄以及“Invoke()”正在查看旧句柄这一事实有关。所以我的问题是,我该如何解决这个问题?

编辑:根据要求,打开表单的代码以及 bg 线程的工作位置

打开 DropLogMDIalt 表单很简单

    FormCTRL.Show()

后台线程在控件被修改时运行,因此 NumericUpDown 大于 0(以便有一些东西可以倒计时)

    Private Sub NLauncherTerminateInput_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles DLScanInterval.ValueChanged
    If DLScanInterval.Created = True Then
        DLTimerControlValue = DLScanInterval.Value
        If DLTimerControlValue = 0 Then
            CancelDropLogTimer()
        Else
            If DLScanIntervalControl.Active = False Then
                BeginDropLogTimer()
            End If
        End If
    End If
End Sub


Public Sub BeginDropLogTimer()
    Dim N As New Threading.Thread(AddressOf DropLogTimerIntervalThreadWorker)
    N.Start()
    DLScanIntervalControl.ThreadID = N.ManagedThreadId
    DLScanIntervalControl.Active = True
End Sub

Public Sub CancelDropLogTimer()
    DLScanIntervalControl.Active = False
End Sub

    Public Sub DropLogTimerIntervalThreadWorker()
    DLScanTimerSecondsLeft = DLTimerControlValue * 60
    Dim s As Integer = DLTimerControlValue
    Do Until 1 = 2
        DLScanTimerSecondsLeft = DLTimerControlValue * 60
        Do Until DLScanTimerSecondsLeft <= 0
            If Not (DLTimerControlValue = 0 Or DLScanIntervalControl.CancelPending = True) Then
            Else
                Exit Sub
            End If

            If Not DLTimerControlValue = s Then
                DLScanTimerSecondsLeft = DLTimerControlValue * 60
                s = DLTimerControlValue
            End If

            Dim ToInvoke As New MethodInvoker(Sub()
                                                  Timer(DLScanTimerSecondsLeft, ":", DLScanIntervalTB)
                                              End Sub)
            If (Me.IsHandleCreated) Then
                If (InvokeRequired) Then
                    Invoke(ToInvoke)
                Else
                    ToInvoke()
                End If
            End If


            Threading.Thread.Sleep(1000)
            DLScanTimerSecondsLeft -= 1
        Loop

        CompareScan = True
        PerformScan()
    Loop
End Sub

通过声明一个新的 thread.thread 来简单地调用线程,但是,我创建了一个类和一个变量,该线程用于检查它是否应该仍在运行(类似于 backgroundworker 的方式)这是由“DLScanIntervalControl.CancelPending”说明

表格随后由

关闭
Form.Close()

如果用户点击一个标签,然后使用与上面所示相同的方法(FormCTRL.Show()),它可以重新打开

【问题讨论】:

  • 我怀疑如果您再次创建控件(包括表单),那么它们与以前的控件不同(即它们具有不同的句柄)。你可以只用.Hide 适当的表格来代替吗? ...啊,是的,来自Form.Close() 的文档:“当关闭表单时,在对象中创建的所有资源都将关闭并释放表单。”
  • 我在尝试使用委托时遇到了类似的错误,我发现只有在“需要”时才创建句柄。我不确定什么构成“需要”,但您可以通过访问对象的 Handle 属性来强制它创建句柄。我在我的应用程序中必须做的是遍历表单上的所有控件并检查 IsHandleCreated 属性。如果它是假的,那么我只是调用“Dim tmp = ctrl.Handle”并继续我的快乐方式。它相当贫民窟,但它可以在这里帮助你(如果你仍然需要帮助)
  • 这是一个典型的例子,说明为什么 UI 应该禁止其他线程。而不是增加后台线程的开销来更新可能存在的 UI。让表单本身以受控的方式适当地处理更新。

标签: vb.net multithreading winforms invoke


【解决方案1】:

来自MSDN

"如果控件的句柄尚不存在,InvokeRequired 会向上搜索控件的父链,直到找到具有窗口句柄的控件或窗体。如果找不到合适的句柄,则 InvokeRequired 方法返回 false 。”

换句话说,您需要验证句柄是否已创建,然后检查是否需要调用。

If(Me.IsHandleCreated) Then
    If(Me.InvokeRequired) Then
        '...
    Else
        '...
    End If
End If

【讨论】:

  • Me.IsHandleCreated 只返回“False”。我指的是 DropLogMDI 是我在此断点处观察到的形式
  • just returns "False" 你能详细说明一下吗?我不确定我是否在关注你。
  • 更新 GUI 的代码已移动到“If (Me.IsHandleCreated) 语句中,但由于 IsHandleCreated 返回为 false,它从未运行过。我的问题可能与后台线程的事实有关吗?代码在 DropLogMDI 类(重新创建的表单)中运行?
  • @user3494322 你应该在你的问题中提供一些代码
  • 可能。您能否更新您的问题以包括您在何处以及如何创建 bg 线程以及您如何引用/调用表单?
【解决方案2】:

在尝试使用委托来更新另一个线程中的表单上的控件时,我遇到了类似的错误。我发现只有在“需要”时才创建句柄。我不确定什么构成“需要”,但您可以通过访问对象的 Handle 属性来强制它创建句柄。 我在申请中所做的是:

  ' Iterate through each control on the form, and if the handle isn't created yet, call the
  ' Handle property to force it to be created
  For Each ctrl As Control In Me.Controls
     While Not ctrl.IsHandleCreated
        Dim tmp = ctrl.Handle
        tmp = Nothing
     End While ' Not ctrl.IsHandleCreated
  Next ' ctrl As Control In Me.Controls

这里相当贫民窟,但它可以帮助你(如果你仍然需要帮助)

【讨论】:

    【解决方案3】:

    我认为这里的问题与调用无关,而是与引用有关。按照这个伪代码...

    Dim A As New Form1
    A.Show()
    
    ''Spawn background thread with a reference to A
    
    A.Dispose()
    
    Dim B As New Form1
    B.Show()
    

    线程正在尝试引用上面已处理的Form1 的第一个实例,并将始终保持这种状态。

    如果您希望线程能够更新任何表单,那么您需要为线程提供一种(同步)方式来引用表单...

    Public Class Worker
    
        Private Target As Form1
        Private TargetLock As New Object
    
        Public Sub SetTargetForm(Frm as Form1)
            SyncLock TargetLock
                Target = Frm
            End SyncLock
        End Sub
    
        Public Sub DoWork() ''The worker thread method
            ''Do work as usual then...
            SyncLock TargetLock
                If Target IsNot Nothing AndAlso Target.IsHandleCreated Then
                    If Target.InvokeRequired
                        Target.Invoke(...)
                    Else
                        ...
                    End If
                End If
            End SyncLock
        End Sub
    End Class
    

    这样,当有新表单可用时,您可以使用SetTargetForm() 通知工作线程,它会更新相应的表单。

    当然,为了简单/可维护性,您最好重构“更新 UI”检查并将调用调用到不同的方法中,但您明白了。

    请注意,我手头没有 IDE,因此可能存在拼写错误。

    最后一点...我会质疑为内存管理目的而处理表单的价值。控件在内存方面是相当轻量级的,并且表单使用的对象比表单本身更容易占用内存。您确定您从这种增加的复杂性中获得了真正的好处吗?

    【讨论】:

    • 谢谢!早上我会好好看看这个。关于 USER/GDI 对象,由此带来的好处保证了我正在尝试做的额外挑战,因为这在我的程序的先前版本中是一个问题,没有任何处理(程序在它们不多的环境中运行)可用的免费对象)
    • 啊,好吧...受限用户/GDI 句柄完全是另一回事。在从数据生成的一些(非常)复杂的表单上,我已经达到了这个限制。让我知道这是否有帮助
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-27
    • 2010-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多