Reputation:
I am trying to understand how delegates and invoke work.
So I build a form, with a label and a button.
When someone clicks on the Button, the Text changes to "Stop" and a counter starts counting up. This counter should be displayed on a Label. This is my code:
Public Class Form1
Private t1 As Thread
Private sek_ As Integer = 0
Private Sub btn_read_Click(sender As Object, e As EventArgs) Handles btn_read.Click
If btn_read.Text = "Read" Then
btn_read.Text = "Stop"
t1 = New Thread(AddressOf stopw_)
t1.Start()
Else
lbl_stopw_.Text = ""
btn_read.Text = "Read"
End If
End Sub
Private Delegate Sub stopw_D()
Private Sub stopw_()
Do While btn_read.Text = "Stop"
sek_ = sek_ + 1
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke(New stopw_D(AddressOf stopw_))
Else
lbl_stopw_.Text = sek_
End If
Thread.Sleep(1000)
Loop
sek_ = 0
If t1.IsAlive Then t1.Abort()
End Sub
End Class
If I start debugging, the form still freezes and the label does not get updated. If I delete all the delegate and invoke stuff and use Me.CheckForIllegalCrossThreadCalls = False
Its working.
What am I doing wrong?
Upvotes: 1
Views: 3764
Reputation: 18310
Control.Invoke()
is used to execute a method on the same thread that the control was created on. Since code can only be executed one line at a time in each thread, executing a method on the control's thread will result in the method being "queued" until the other code in that thread, prior to the method, has completed. This makes it thread-safe as there will be no concurrency issues.
A Delegate
is simply a class holding the pointer to a method. It exists so that you can use methods as if they were ordinary objects (in this case you pass it to a function). The AddressOf
operator is a quick way of creating a delegate.
Now, you have a few issues in your code. First of all, you should not try to access or modify ANY UI element from a background thread. Whenever you want modify or check a control you must always invoke.
More specifically, I'm talking about your While
-loop:
'You can't check the button here without invoking.
Do While btn_read.Text = "Stop"
It is better if you create a Boolean
variable that indicates when the thread should run.
Private t1 As Thread
Private sek_ As Integer = 0
Private ThreadActive As Boolean = False
Set ThreadActive
to True
before you start the thead, then in your thread's While
-loop check:
Do While ThreadActive
Now, there is another issue. Your UI freezes because of this:
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke(New stopw_D(AddressOf stopw_))
NEVER invoke the the same method which the thread runs on! Doing so starts the processing all over again, but on the UI thread. Your loop makes the UI thread completely busy, which is why it doesn't redraw itself.
So when you are to update something, always invoke a seperate method. If you target .NET 4.0 or higher you can use lambda expressions for a quick, inline delegate:
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke( _
Sub()
lbl_stopw_.Text = sek_
End Sub)
Else
lbl_stopw_.Text = sek_
End If
However if you are targeting .NET 3.5 or lower you have to stick to the normal way of using delegates:
'Outside your thread.
Private Delegate Sub UpdateLabelDelegate(ByVal Text As String)
Private Sub UpdateLabel(ByVal Text As String)
lbl_stopw_.Text = Text
End Sub
'In your thread.
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke(New UpdateLabelDelegate(AddressOf UpdateLabel), sek_)
Else
UpdateLabel(sek_)
End If
Alternatively, in order to minimize the amount of code you have to write you can create an extension method to do the invoking for you:
Imports System.Runtime.CompilerServices
Public Module Extensions
<Extension()> _
Public Sub InvokeIfRequired(ByVal Control As Control, ByVal Method As [Delegate], ByVal ParamArray Parameters As Object())
If Parameters Is Nothing OrElse _
Parameters.Length = 0 Then Parameters = Nothing 'If Parameters is null or has a length of zero then no parameters should be passed.
If Control.InvokeRequired = True Then
Control.Invoke(Method, Parameters)
Else
Method.DynamicInvoke(Parameters)
End If
End Sub
End Module
Usage, .NET 4.0 or higher:
lbl_stopw_.InvokeIfRequired( _
Sub()
lbl_stopw_.Text = sek_
End Sub)
Usage, .NET 3.5 or lower:
lbl_stopw_.InvokeIfRequired(New UpdateLabelDelegate(AddressOf UpdateLabel), sek_)
When using this extension method you don't need to write InvokeRequired
checks everywhere:
Do While btn_read.Text = "Stop"
sek_ = sek_ + 1
lbl_stopw_.InvokeIfRequired(New UpdateLabelDelegate(AddressOf UpdateLabel), sek_)
Thread.Sleep(1000)
Loop
And finally, this is just unnecessary:
If t1.IsAlive Then t1.Abort()
The thread will always be alive when it reaches that If
-statement since it hasn't exited the stopw_
method yet. But once the thread has exited the method it will end normally, so there's no reason to call Abort()
.
The answer got a bit long, but I hope it to be helpful!
Upvotes: 4