Reputation: 4331
I have a button-click event handler that among other things updates a private, non-shared instance variable in the containing Form.
I also have a System.Windows.Forms.Timer, whose Tick event comes around a few seconds after that button click event has completed.
My question: Why does the Tick event handler sometimes (quite often) see the previous value of that instance variable? (I thought that System.Windows.Forms.Timer is thread-safe with regard to instance variables.)
Related question: Is it relevant that this happens frequently on a very fast quad-processor computer but rarely if ever on a slow dual-processor? In other words, is it possible that the issue has something to do with synchronizing instance variables across CPUs?
Code follows. Comment conventions modified for display beauty.
/* Instance variable get/set */
Public Property mode() As modetype
Get
Return _mode
End Get
Set(ByVal value As modetype)
_mode = value
Select Case value
/* Lots of mode-specific processing here */
End Select
Debug.Assert(mode = value)
End Set
End Property
/* Click event handler */
Private Sub btnClear_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnClear.Click
Debug.Assert(Not (picVideo Is Nothing))
mode = modetype.clear
End Sub
/* Tick event handler */
Private Sub tmrCapture_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles tmrLiveImageCapture.Tick
// FOLLOWING LINE is where mode should be .clear but comes up as .live instead.
If mode = modetype.live Then
Debug.Assert(mode = modetype.live) // Seriously? Yes.
Try
execute_timer_tick_stuff()
Catch ex As Exception
/* Shouldn't happen */
tmrLiveImageCapture.Stop() // FIXME: can you stop a timer within its own tick event?
MessageBox.Show("Error in live timer tick: " & ex.Message)
Debug.Assert(Not tmrLiveImageCapture.Enabled)
End Try
End If
End Sub
Thanks.
Upvotes: 3
Views: 427
Reputation: 8354
In order to make sure only one block of code executes at the same time, use SyncLock. This will prevent the value of mode from catching up during the tick event.
You need a unique reference type instance to serve as a key to the group:
Dim TestSyncLock As New Object()
Now only one of the following blocks can execute at a time; the others wait until an entire SyncLock block is completed before another SyncLock gets a chance to execute.
SyncLock TestSyncLock
DoSomethingTricky()
End SyncLock
SyncLock TestSyncLock
DoSomethingElseTricky()
End SyncLock
Executing an entire block of code at once without interruption is called an atomic operation. Try this for your code:
Private modeSyncLock As New Object()
/* Instance variable get/set */
Public Property mode() As modetype
Get
Return _mode
End Get
Set(ByVal value As modetype)
/* If we have entered the tick handler's synclock, wait until it's done */
SyncLock modeSyncLock
_mode = value
End SyncLock
Select Case value
/* Lots of mode-specific processing here */
End Select
Debug.Assert(mode = value)
End Set
End Property
/* Click event handler */
Private Sub btnClear_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnClear.Click
Debug.Assert(Not (picVideo Is Nothing))
mode = modetype.clear
End Sub
/* Tick event handler */
Private Sub tmrCapture_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles tmrLiveImageCapture.Tick
/* If we have entered the mode set, wait until it's done before proceeding */
SyncLock modeSyncLock
If mode = modetype.live Then
Debug.Assert(mode = modetype.live) // Seriously? Yes.
Try
execute_timer_tick_stuff()
Catch ex As Exception
/* Shouldn't happen */
tmrLiveImageCapture.Stop() // FIXME: can you stop a timer within its own tick event?
MessageBox.Show("Error in live timer tick: " & ex.Message)
Debug.Assert(Not tmrLiveImageCapture.Enabled)
End Try
End If
End SyncLock
End Sub
Upvotes: 1