Search code examples
.netvb.netmultithreadinginvoke

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


I'm working in VB.NET 4.8.1 I created a function InvokeIfNecessary(action) which checks if it's running on the UI thread and invokes to the main form if not.

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

Because it's worked really well, I've started replacing my mess of .InvokeRequired and other checks with this simple function. However I just got the error "Cross-thread operation not valid: Control 'PB_ItemImage' accessed from a thread other than the thread it was created on."

It's failing in the code below on _img.Visible=false. I've paused execution right before to confirm it was running in the UI thread. The variable refers to a PictureBox which I created in the designer, so it should also be in the UI thread, obviously. (I also confirmed that the function had not been on the UI Thread, and so it is using the Invoke function.)

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

This has been working elsewhere. What could be different to cause this error? Is there some reason why I shouldn't invoke on OpenForms(0)? Why would _img.InvokeRequired be true inside an Invoke function?

This is executing as part of a Form.Load event, inside a Task that is downloading the image file.

Ed. to add IsUIThread() which checks if the main form requires invoking under the assumption that all UI controls are on the same thread:

Public Function IsUIThread() As Boolean
    If Application.OpenForms.Count = 0 Then Return Nothing
    Return Not Application.OpenForms(0).InvokeRequired
End Function

Solution

  • Here is an example of how you might write an extension method to do what you're trying to do:

    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
    

    You would then use that like this:

    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