[Control].Invoke doesn't always return

L

Lucvdv

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
 
P

Peter Huang

Hi Lucvdv,

When a thread is in sleeping status, the thread status should be
WaitSleepJoin, so when in the form closing event, the thread.abort may not
be called, and when the form is closed, the working thread is still try to
call the invoke on the main thread.
ThreadState Enumeration
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/
frlrfsystemthreadingthreadstateclasstopic.asp

Here is my test code snippet, you may have a try to see if this helps you.
Dim rm As New Random
Private Sub InvokeItem()
If Me.ListBox1.InvokeRequired Then
Debug.WriteLine(Thread.CurrentThread.Name & " Invoke AddItem")
Me.Invoke(New MethodInvoker(AddressOf AddItem))
Else
Debug.WriteLine(Thread.CurrentThread.Name & " AddItem")
AddItem()
End If
End Sub
Private Sub AddItem()
Debug.WriteLine(Thread.CurrentThread.Name)
If ListBox1.Items.Count > 100 Then
ListBox1.Items.Clear()
End If
Me.ListBox1.Items.Add(rm.Next().ToString())
End Sub
Private Sub ThreadProc()
Try
While True
Thread.Sleep(1000)
InvokeItem()
End While
Catch abortException As ThreadAbortException
Debug.WriteLine(abortException.ToString())
'Do necessary cleaning job here
End Try
End Sub
Dim th As New Thread(AddressOf ThreadProc)
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Thread.CurrentThread.Name = "Main Thread"
th.Name = "Working Thread"
th.Start()
End Sub

Private Sub Form1_Closing(ByVal sender As Object, ByVal e As
System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
If Not th.ThreadState = System.Threading.ThreadState.Stopped Then
th.Abort()
End If
th.Join()
End Sub

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button2.Click
InvokeItem()
Me.ListBox1.Items.Add(th.ThreadState.ToString())
End Sub

Best regards,

Peter Huang
Microsoft Online Partner Support

Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
 
L

Lucvdv

Hi Lucvdv,

When a thread is in sleeping status, the thread status should be
WaitSleepJoin, so when in the form closing event, the thread.abort may not
be called, and when the form is closed, the working thread is still try to
call the invoke on the main thread.

Thanks a lot: that was it.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top