Search code examples
vb.netmultithreadingwinformsformclosing

Properly closing a form running a loop in a new thread


I have method in my form that is running a do while loop in a new thread.

That for loop exits once a sentinel value is set to true (set by another method that handles Me.FormClosing)

The issue is that when the form closes, I get occasionally get two exceptions.

ObjectDisposedException and ComponentModel.Win32Exception.

How do I properly exit a form without "eating" these exceptions and ignoring them.

Code:

Dim _exit As Boolean

Public Sub test()
    Dim task As Thread = New Thread(
            Sub()
                Do
                    checkInvoke(Sub() a.append("a"))
                Loop While _exit = False
            End Sub)
End Sub

Private Sub checkInvoke(ByVal _call As Action)
    If Me.InvokeRequired Then
        Me.Invoke(Sub() checkInvoke(_call))
    Else
        _call.Invoke()
    End If
End Sub

Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
    _exit = True
End Sub

Solution

  • Where does the error come from ?

    This can be a bit confusing but it actually is pretty logical...

    1. The user (or something else) closes the form.
    2. FormClosing is then called which sets _exit to True
    3. Then the Form closes itself, destroying its handle.
    4. Now, it depends where it sometimes throws an exception :
      • Either the Thread just finished the Invoke or the Loop, check the _exit value then ends the loop, everything goes fine.
      • Either it just began the Invoke, then it calls a method invoking the UI thread to modify something on the form that has just been disposed, no more Handle to this form, leading to ObjectDisposedException

    How to prevent this ?

    One thing you can do is, in your FormClosing event, wait for the Thread to end, then letting the system close the form :

    Private _isFinished As Boolean = False
    Private _exit As Boolean = False
    
    Public Sub test()
        Dim task As Thread = New Thread(
                Sub()
                    Do
                        checkInvoke(Sub() a.append("a"))
                    Loop While _exit = False
                    'We inform the UI thread we are done
                    _isFinished = True
                End Sub)
    End Sub
    
    
    Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
        _exit = True
        While Not _isFinished
            Application.DoEvent() 'We can't block the UI thread as it will be invoked by our second thread...
        End While
    End Sub