catfood
catfood

Reputation: 4331

System.Windows.Forms.Timer and instance variables

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

Answers (1)

jnm2
jnm2

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

Related Questions