user3494322
user3494322

Reputation: 311

vb.net problems with invoke

I have a thread that runs background jobs and is required to update the GUI once in a while. My program has been designed so that when the user clicks off of a form, the thread and background operations still run, yet the controls have been disposed (for memory management purposes).

I have been using Invoke() and "If Control.Created = True" to make sure that the thread can successfully update the controls without running into any exceptions. However, when the form is recreated, all "Control.Created" values are false and Invoke() fails with "{"Invoke or BeginInvoke cannot be called on a control until the window handle has been created."}"

My guess is that this has something to do with the fact that when the form is recreated it is assigned different handles and that the "Invoke()" is looking at the old handle. SO my question is, how do I fix this?

EDIT: As per requested, the code for opening the form and where the bg thread works from

Opening the DropLogMDIalt form is simply

    FormCTRL.Show()

The Background Thread runs when the control is modified so that the NumericUpDown is more than 0 (so that there is something to countdown from)

    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

The thread is simply called by declaring a new thread.thread, however, I have created a class and a variable that the thread uses to check if it should still be running or not (similarly to how a backgroundworker would) this is illustrated by the "DLScanIntervalControl.CancelPending"

The form is then closed later by

Form.Close()

It can be reopened if the user clicks on a label that then uses the same method as shown above (FormCTRL.Show())

Upvotes: 4

Views: 1303

Answers (3)

Taegost
Taegost

Reputation: 1216

I had a similar error when trying to use delegates to update controls on a form in another thread. I found that the handle is only created when it's "needed". I'm not sure what constitutes "needed", but you can force it to create the handle by accessing the Handle property of the object. What I had done in my application is this:

  ' 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

It's rather ghetto, but it may help you here (If you still need the help)

Upvotes: 1

Basic
Basic

Reputation: 26756

I think the issue here has nothing to do with invoking, but references. Following this pseudocode...

Dim A As New Form1
A.Show()

''Spawn background thread with a reference to A

A.Dispose()

Dim B As New Form1
B.Show()

The thread is attempting to refer to the first instance of Form1 above which is disposed and will always stay that way.

If you want the thread to be able to update any form then you need to give the thread a (synchronised) way to refer to the form...

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

This way, when a new form is available, you can inform the worker thread using SetTargetForm() and it will update the appropriate one.

Of course, you'd be better off refactoring the "Update UI" checks and invoke calls into a different method for simplicity/maintainability but you get the point.

Note that I haven't got an IDE to hand so there may be typos.

One final point... I'd question the value of disposing a form for memory management purposes. Controls are fairly lightweight in terms of memory and it's far more common for an object used by the form to be a memory hog than the form itself. Are you sure you're getting a real benefit for this added complexity?

Upvotes: 0

From MSDN:

"If the control's handle does not yet exist, InvokeRequired searches up the control's parent chain until it finds a control or form that does have a window handle. If no appropriate handle can be found, the InvokeRequired method returns false."

In other words, you need to verify that the handle is created and then check if invoke is required.

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

Upvotes: 1

Related Questions