在 Invoke() 中运行时,跨线程操作在窗体控件上无效 - .NET

Cross-thread operation not valid on form control when running inside Invoke() - .NET

提问人:Scott 提问时间:4/14/2023 最后编辑:Scott 更新时间:4/14/2023 访问量:64

问:

我正在使用 VB.NET 4.8.1 我创建了一个函数 InvokeIfNecessary(action),它检查它是否在 UI 线程上运行,如果没有,则调用主窗体。

Public Sub InvokeIfNecessary(act As Action)
    If IsUIThread() Then
        act()
    Else
        Application.OpenForms(0).Invoke(act)
    End If
End Sub

因为它效果非常好,所以我开始替换我的烂摊子。InvokeRequired 和其他检查与这个简单函数。但是,我只是收到错误“跨线程操作无效:从创建它的线程以外的线程访问”控制'PB_ItemImage'。

它在下面的代码中失败了_img。Visible=false。我之前暂停了执行,以确认它正在 UI 线程中运行。该变量引用了我在设计器中创建的 PictureBox,因此显然它也应该位于 UI 线程中。(我还确认该函数在 UI 线程上,因此它正在使用 Invoke 函数。

Public Function ImgDisplayFromFile(ByRef img As PictureBox, imgFileName As String, Optional safe As Boolean = False)
    Dim _img = img
    If Not _img.IsDisposed Then InvokeIfNecessary(Sub()
                                                      MsgBox(IsUIThread()) 'This confirmed True
                                                      MsgBox(_img.InvokeRequired()) 'This is also true??
                                                      _img.Visible = False
                                                      DoOtherStuff()
                                                  End Sub)
End Function

这在其他地方已经奏效了。导致此错误的原因可能是什么?有什么理由不应该在OpenForms(0)上调用吗?为什么会_img。InvokeRequired 在 Invoke 函数中是否为 true?

这是作为 Form.Load 事件的一部分在下载图像文件的 Task 中执行的。

添加 IsUIThread(),在假设所有 UI 控件都位于同一线程上的情况下检查主窗体是否需要调用:

Public Function IsUIThread() As Boolean
    If Application.OpenForms.Count = 0 Then Return Nothing
    Return Not Application.OpenForms(0).InvokeRequired
End Function
.NET vb.net 多线程调用

评论

0赞 Enigmativity 4/14/2023
您还没有向我们展示,但是由于要回来,那么一定是在不同的线程上创建的。IsUIThread_img.InvokeRequired()true_img
0赞 Scott 4/14/2023
添加了该功能。我不反对,我只是不明白如果 _img 控件是用设计器而不是在代码中创建的,那该如何
0赞 Hans Passant 4/14/2023
使用 Application.OpenForms(0)。在工作线程上创建表单对象时,InvokeRequired无法工作。这在 vb.net 中经常发生,它的默认实例功能相当致命。在窗体构造函数上设置断点以查找创建调用。
0赞 jmcilhinney 4/14/2023
这就是为什么您应该使用要对其执行操作的控件的 and 成员,而不是每次都使用相同的窗体。只需更改以允许您也传入控件,然后直接使用它并完全摆脱它。事实上,有些人将这样的方法编写为扩展方法,因此您实际上在要对其执行操作的控件上调用该方法。InvokeRequiredInvokeInvokeIfRequiredIsUIThread
1赞 jmcilhinney 4/14/2023
也就是说,真正的问题可能是您首先在错误的线程上创建包含该表单的表单。您应该检查该代码以及运行在哪个线程上。PictureBox

答:

2赞 jmcilhinney 4/14/2023 #1

下面是一个示例,说明如何编写扩展方法来执行您尝试执行的操作:

Imports System.Runtime.CompilerServices

Public Module ControlExtensions

    <Extension>
    Public Sub InvokeIfRequired(source As Control, method As Action)
        If source.InvokeRequired Then
            source.Invoke(method)
        Else
            method()
        End If
    End Sub

End Module

然后,您可以像这样使用它:

Public Sub ImgDisplayFromFile(img As PictureBox, imgFileName As String, Optional safe As Boolean = False)
    Dim _img = img

    If Not _img.IsDisposed Then _img.InvokeIfRequired(Sub()
                                                          _img.Visible = False
                                                          DoOtherStuff()
                                                      End Sub)
End Sub

评论

1赞 Scott 4/14/2023
我喜欢这个解决方案,谢谢,虽然我有点失望,因为我认为我有办法在需要时在一个方法中安全地访问多个控件,但似乎示例中的方法应该只与控件源交互
0赞 jmcilhinney 4/14/2023
@Scott,不可以,此解决方案适用于任意数量的控件。该控件仅专门用于其 和 成员。可以访问任意数量的控件。人们会认为这些控件通常与控件处于同一形式。即使它们不是,只要它们在同一线程上创建,它仍然可以工作。如果控件位于不同线程中创建的不同窗体上,您仍然会遇到问题,但这应该是一件非常罕见的事情。sourceInvokeRequiredInvokemethodsource