【问题标题】:Cross-Thread operation not valid VB.NET跨线程操作无效 VB.NET
【发布时间】:2014-06-20 15:49:26
【问题描述】:

我浏览了网站,发现与此主题相关的问题是针对 C#(我正在维护的应用程序是用 VB.NET 编写的),所以如果我忽略了一个,我深表歉意。

这里是我调用我的线程的地方:

Private Sub saveBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles saveBtn.Click
    If Not LoadedFilePath = String.Empty Then
        Dim oTrd = New Threading.Thread(AddressOf SaveData)
        oTrd.Start()
    End If
End Sub

以下是方法:

Private Sub SaveData()
    SaveData(LoadedFilePath)
End Sub
Private Sub SaveData(ByVal filepath As String)
    If InvokeRequired Then
        Me.Invoke(New MethodInvoker(AddressOf SaveData))
    End If
    Try
        Me.Cursor = Cursors.WaitCursor
        Dim oSettings As New SettingsClass(filepath)
        Dim oEnc As New AES
        With oSettings
            //' Code removed for brevity
        End With
        oEnc = Nothing
        oSettings.SaveSettings()
        savedLbl.Visible = True
        If SavedTimeout IsNot Nothing Then
            Try
                SavedTimeout.StopEvent()
            Catch
            End Try
        End If
        SavedTimeout = New TimedEvent(Now.AddSeconds(5))
        SavedTimeout.StartEvent()
        Me.Cursor = Cursors.Default
    Catch ex As Exception
        MsgBox(ex.Message)
    End Try
End Sub

保存功能工作正常,但是当程序尝试将光标切换回默认值时出现跨线程错误。我该怎么做才能解决这个问题?

【问题讨论】:

    标签: vb.net multithreading


    【解决方案1】:

    您在所有者 (GUI) 线程中调用该方法的方式是错误的。如果需要调用,则不应执行方法中的其余代码。如果这样做,您将在 GUI 线程和后台线程中执行它,并且当您尝试从后台线程访问 GUI 元素时,您会收到跨线程错误。

    调用应该如下所示:

    Private Sub SaveData(ByVal filepath As String)
       If InvokeRequired Then
          Me.Invoke(New MethodInvoker(AddressOf SaveData))
       Else
          ... the actual code
       End If
    End Sub
    

    但是,当它必须在 GUI 线程中调用自身时,为什么要在后台线程中启动该方法?

    【讨论】:

    • 哇哈哈我不敢相信我忘记了其他...一天结束的FTL!谢谢你的解决方案:)
    • @Anders:就我个人而言,我会在没有Else 的情况下保留它,并在Me.Invoke() 之后添加一个Return 以阻止该方法进一步执行。它通过减少嵌套来帮助保持代码易于阅读。
    • @yoooder:另一方面,如果方法只有一个退出点,则更容易遵循结构。我会说任何一种方式在客观上并不比另一种更好。
    • 我建议不要使用 RECURSION,而是编写一个包装函数:Sub SaveData(..params..) If InvokeRequired Then Invoke(Sub(..params..) SaveDataAlreadyOnUIThread(..params..)) Else SaveDataAlreadyOnUIThread(..params..) End If End Sub。 --plus--Private Sub SaveDataAlreadyOnUIThread(..params..) ..do-the-actual-work.. End Sub。这对于任何新程序员来说都更容易快速理解。特别是不言而喻,工作总是只完成一次。而递归版本需要花费更多的脑力。
    • 注意:编写包装器的另一种方法是 Sub SaveData(..params..) If InvokeRequired Then Invoke((MethodInvoker)delegate { SaveDataAlreadyOnUIThread(..params..) } Else SaveDataAlreadyOnUIThread(..params..) End If End Sub 这有点简单 - 不必像我在之前的评论中那样重复 ..params..。如果它不明显,在原始帖子中,它将是filePath as String,我说..params....s 是我说“随便”的方式。
    【解决方案2】:

    您启动了一个不允许调用用户界面的辅助线程。您只能从 UI-Thread 本身设置光标。

    要实现这一点,您必须从帮助线程中触发一个事件,该事件告诉 UI 线程您的工作已经完成,并且它可以将光标设置回来,或者通过调用 UI 线程来执行此操作:

    private void ResetCursor()
    {
        this.Cursor = Cursor.Default;
    }
    
    private delegate void UpdateCursor();
    private void SaveData()
    {
        //Do your work here
        if(this.InvokeRequired)
        {
            this.Invoke(new UpdateCursor(ResetCursor));
        }
        else
        {
            ResetCursor();
        }
    }
    

    【讨论】:

      【解决方案3】:

      在多线程 Windows 窗体应用程序中,从创建控件的线程以外的任何线程调用控件上的方法或属性都是非法的。必须使用 Control.Invoke 或 Control.BeginInvoke 方法将所有跨线程调用显式编组到创建控件的线程(通常是主线程)。

      这里有一个网页可以帮助你解决这个问题:

      http://www.dreamincode.net/forums/showtopic35616.htmC#

      http://www.codeproject.com/KB/vb/ISinchronizedInvoke.aspxVB.NET

      【讨论】:

        【解决方案4】:

        此网页包含从单独线程访问表单控件所需的精简代码: http://www.databatrix.com/2009/09/cross-thread-operation-not-valid-net_4280.html?q=cross+thread

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-11-02
          • 2011-07-11
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多