Worker thread blocking on calls to .Invoke

J

Jeff Stewart

I need a thread to run a subroutine which updates my main form's
progress bar. I've properly marshaled all UI updates to the main UI
thread, and after the main thread starts the worker thread, it waits
for the worker thread to complete by means of a while t.isAlive,
sleep(0) mechanism. But when my worker thread calls my
UpdateProgressBar routine, which calls Me.Invoke, the invoke call
blocks forever. But I can't figure out why the main thread never
services the invoke call.

--
Jeff S.


Here's a sample program:

Public Class Form1
Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub

'Form overrides dispose to clean up the component list.

Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)

If disposing Then

If Not (components Is Nothing) Then

components.Dispose()

End If

End IfPublic Class Form1
Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

Public Sub New()
MyBase.New()

'This call is required by the Windows Form Designer.
InitializeComponent()

'Add any initialization after the InitializeComponent() call

End Sub

'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As
Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub

'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer

'NOTE: The following procedure is required by the Windows Form
Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Friend WithEvents ProgressBar1 As System.Windows.Forms.ProgressBar
Friend WithEvents Button1 As System.Windows.Forms.Button
<System.Diagnostics.DebuggerStepThrough()> Private Sub
InitializeComponent()
Me.ProgressBar1 = New System.Windows.Forms.ProgressBar
Me.Button1 = New System.Windows.Forms.Button
Me.SuspendLayout()
'
'ProgressBar1
'
Me.ProgressBar1.Location = New System.Drawing.Point(8, 40)
Me.ProgressBar1.Name = "ProgressBar1"
Me.ProgressBar1.TabIndex = 0
'
'Button1
'
Me.Button1.Location = New System.Drawing.Point(8, 8)
Me.Button1.Name = "Button1"
Me.Button1.Size = New System.Drawing.Size(96, 23)
Me.Button1.TabIndex = 1
Me.Button1.Text = "Go"
'
'Form1
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(112, 69)
Me.Controls.Add(Me.Button1)
Me.Controls.Add(Me.ProgressBar1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)

End Sub

#End Region

Dim t As Threading.Thread

Private Delegate Sub UpdateProgressDelegate(ByVal value As
Integer)

Private Sub UpdateProgress(ByVal value As Integer)
If Me.ProgressBar1.InvokeRequired Then
Dim delegateObj As UpdateProgressDelegate = New
UpdateProgressDelegate(AddressOf UpdateProgress)
Me.ProgressBar1.Invoke(delegateObj, New Object() {value})
Else
Me.ProgressBar1.Value = value
End If
End Sub

Private Sub ThreadRunner()
For i As Integer = 1 To 100
Me.UpdateProgress(i)
Threading.Thread.CurrentThread.Sleep(1000)
Next
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e
As System.EventArgs) Handles Button1.Click
Me.t = New Threading.Thread(AddressOf ThreadRunner)
t.Start()
End Sub
End Class
 
H

Hayato Iriumi

Hello Jeff,

I understand what you're trying to do. Instead of trying to manipulate Windows
Form from another thread, why don't you fire an event and have the Windows
Form that's running in the main thread subscribe to it so that you won't
have the thread issue? I've done something similar to what you're trying
to accomplish and successfully done it. Please let me know if you have further
question.
 
T

Tom Shelton

Hello Jeff,

I understand what you're trying to do. Instead of trying to manipulate Windows
Form from another thread, why don't you fire an event and have the Windows
Form that's running in the main thread subscribe to it so that you won't
have the thread issue? I've done something similar to what you're trying
to accomplish and successfully done it. Please let me know if you have further
question.

The event, if fired from a separate thread will not necessarilly be in
the forms thread. Though it may appear to work, you may in fact be
looking down the barrel of a crash :) You should check the
InvokeRequired property of the form in the event to make sure that it is
safe to directly update that form.
 
H

Hayato Iriumi

Hello Tom,

I just created a sample on my local machine and stepped through the code
with Thread window open. You're right about that. I've been misunderstanding
it. Even when you use event, you still need Me.Invoke() to change Form in
the main thread although I think it's a clean way to handle things.

I also just took a look at my code that I did for work and I AM doing Invoke()
in the event (CallBack) to alter the Form in the main thread. What was I
thinking when I posted the previous post...?

To make it up, here is Chris Sells' good article about Windows Forms Multithreading.

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnforms/html/winforms06112002.asp

Sorry for the confusion.
 
D

David

I need a thread to run a subroutine which updates my main form's
progress bar. I've properly marshaled all UI updates to the main UI
thread, and after the main thread starts the worker thread, it waits
for the worker thread to complete by means of a while t.isAlive,
sleep(0) mechanism.

Really? Although your example has two identical constructors, there's
no call to IsAlive in there, and no call to Sleep on the main thread.
Are there other differences between your sample code and what you
posted?

Anyway, the posted code works fine for me, although with the
ProgressBar1.Width set so small, you'll only see the bar move every 15
seconds or so, so it might seem like the Invoke isn't getting processed
unless you wait around for it.

Also, you might want to set the IsBackground property of the thread, or
have some other means of thread synchronization, otherwise the secondary
thread will hang around even after the user has closed the form.


But when my worker thread calls my
 
J

Jeff Stewart

David said:
Really? Although your example has two identical constructors, there's
no call to IsAlive in there, and no call to Sleep on the main thread.
Are there other differences between your sample code and what you
posted?

Rats! I changed the code right before I copy/pasted it into the
group. *groan* Here's what I meant to post:
--
Jeff S.


Public Class Form1
Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

Public Sub New()
MyBase.New()

'This call is required by the Windows Form Designer.
InitializeComponent()

'Add any initialization after the InitializeComponent() call

End Sub

'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As
Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub

'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer

'NOTE: The following procedure is required by the Windows Form
Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Friend WithEvents ProgressBar1 As System.Windows.Forms.ProgressBar
Friend WithEvents Button1 As System.Windows.Forms.Button
<System.Diagnostics.DebuggerStepThrough()> Private Sub
InitializeComponent()
Me.ProgressBar1 = New System.Windows.Forms.ProgressBar
Me.Button1 = New System.Windows.Forms.Button
Me.SuspendLayout()
'
'ProgressBar1
'
Me.ProgressBar1.Location = New System.Drawing.Point(8, 40)
Me.ProgressBar1.Name = "ProgressBar1"
Me.ProgressBar1.TabIndex = 0
'
'Button1
'
Me.Button1.Location = New System.Drawing.Point(8, 8)
Me.Button1.Name = "Button1"
Me.Button1.Size = New System.Drawing.Size(96, 23)
Me.Button1.TabIndex = 1
Me.Button1.Text = "Go"
'
'Form1
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(112, 69)
Me.Controls.Add(Me.Button1)
Me.Controls.Add(Me.ProgressBar1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)

End Sub

#End Region

Dim t As Threading.Thread

Private Delegate Sub UpdateProgressDelegate(ByVal value As
Integer)

Private Sub UpdateProgress(ByVal value As Integer)
If Me.ProgressBar1.InvokeRequired Then
Dim delegateObj As UpdateProgressDelegate = New
UpdateProgressDelegate(AddressOf UpdateProgress)
Me.ProgressBar1.Invoke(delegateObj, New Object() {value})
Else
Me.ProgressBar1.Value = value
End If
End Sub

Private Sub ThreadRunner()
For i As Integer = 1 To 100
Me.UpdateProgress(i)
Next
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e
As System.EventArgs) Handles Button1.Click
Me.t = New Threading.Thread(AddressOf ThreadRunner)
t.Start()
While t.IsAlive
Threading.Thread.Sleep(0)
End While
End Sub
End Class
 
S

Stephen Martin

When your worker thread calls Me.Invoke that sends a message to Me's window
in order to marshal the call to the thread that created Me's window. But,
you have that thread in a tight loop calling Sleep(0) so it can never
process the message. So your main thread is waiting for your worker thread
to complete and your worker thread is waiting for your main thread to
process the Invoke message. Deadlock!

The problem is the t.isAlive/Sleep(0) loop. Why do you have that in there? I
can't think offhand why it would ever be useful. There are a number of good
ways to synchronize threads but that isn't one of them. In this case the
main thread needs to return to it's message loop in order to process the
Invoke call (i.e. let it return from whatever event handler it is currently
executing). Not only does this let the Invoke call proceed properly, it
leaves the UI responsive while the worker thread is performing its work,
which is usually the point of a worker thread in this situation.

If you really need that Sleep(0) loop you can get it to process messages by
throwing an Application.DoEvents into the loop. But, trust me that would be
a nasty bit of code.
 

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