To avoid a temporarily frozen user interface, I'm using a separate thread
to fill a list with items found in a database (there can be from a few up
to about 1000 or 1500 items).
There seems to be a problem in this: if the form containing the list is
closed before the worker thread has finished, the Invoke call used to add
entries to the list never returns.
I'm trying to avoid it in any way I can imagine, but somehow it keeps
getting stuck 100% the times the form is closed before the thread exits.
Do I need to use a critical section around all access to a control
(including closing the form), besides using Invoke to access it from a
thread?
In addition to the check for IsDisposed in the code below, I'm setting a
variable 'StopLoading' to true when the form is being closed, which makes
the thread exit as soon as it sees it; _AND_ I'm calling thread.Abort() in
the form's Closing event.
Private Delegate Sub dlg_AddLstIDEntry(ByVal Item As String)
Private Sub AddLstIDEntry(ByVal Item As String)
If Not lstID.IsDisposed Then
If lstID.InvokeRequired Then
Dim dlg As New dlg_AddLstIDEntry(AddressOf AddLstIDEntry)
lstID.Invoke(dlg, New String() {Item}) ' <<< never returns
Else
lstID.Items.Add(Item)
End If
End If
End Sub
I checked by putting a breakpoint on the first line, the sub isn't calling
itself recursively either (anyway, if it was, I would have had a stack
overflow instead of an application that won't stop).
In fact, there are three such worker threads of which more than one can be
running depending on what tabs in a form the user has visited, and when.
This is thee form's "Closing" event handler:
Private Sub frmNavigate_Closing(ByVal sender As Object, _
ByVal e As System.ComponentModel.CancelEventArgs) _
Handles MyBase.Closing
StopLoading = True
If Not thrh_LoadID Is Nothing _
AndAlso thrh_LoadID.ThreadState = ThreadState.Running _
Then thrh_LoadID.Abort()
If Not thrh_LoadCard Is Nothing _
AndAlso thrh_LoadCard.ThreadState = ThreadState.Running _
Then thrh_LoadCard.Abort()
If Not thrh_LoadName Is Nothing _
AndAlso thrh_LoadName.ThreadState = ThreadState.Running _
Then thrh_LoadName.Abort()
End Sub
The threads are started like this:
If Not TabInitialized(0) AndAlso _
(thrh_LoadID Is Nothing _
OrElse thrh_LoadID.ThreadState <> ThreadState.Running) _
Then
lstID.Items.Clear()
thrh_LoadID = New Thread(AddressOf thr_LoadID)
thrh_LoadID.Start()
End If
They each contain a loop that scans through the result of an SQL query and
adds the items it finds to either a list or a tree through Invoke wrappers
like the one above.
Private Sub thr_LoadID()
Dim cmd As SqlClient.SqlCommand
Dim rdr As SqlClient.SqlDataReader
Dim cn2 As SqlClient.SqlConnection = CloneSQLConnection()
If Not TabInitialized(0) _
AndAlso Not cn2 Is Nothing _
AndAlso cn2.State = ConnectionState.Open Then
Try
TabInitialized(0) = True
SetTabCaption(0, "ID (loading)")
cmd = New SqlClient.SqlCommand("SELECT ID From Players", cn2)
rdr = cmd.ExecuteReader
Try
While rdr.Read And Not StopLoading
AddLstIDEntry(rdr.GetString(0))
If CurrentTab = 0 Then
Thread.Sleep(1) ' Takiteezee or UI freezes
Else
Thread.Sleep(20) ' Not current tab: slower
End If
End While
Catch ex As Exception
Debug.WriteLine(ex.ToString)
Finally
rdr.Close()
End Try
Catch ex As Exception
Debug.WriteLine(ex.ToString)
Finally
cn2.Close()
SetTabCaption(0, "ID Card")
End Try
End If
End Sub