3

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.

catfood
  • 4,267
  • 5
  • 29
  • 55
  • It could be several things. Could we see your code? Also, the timer tick event is triggered by windows messages and executed in the same thread as the button click event, so this isn't a multithreading problem. – jnm2 Jun 16 '11 at 20:50
  • It's a lot of code. I added an illustrative subset. Thanks for your comment. – catfood Jun 16 '11 at 21:06
  • I would almost think that your button event should be either MouseUp or KeyUp. Not that this will fix the problem - as I think there are clocking differences that can be easily seen with a `Timer`. Just a thought ... – IAbstract Jun 16 '11 at 21:16
  • I don't see a problem with what you posted. I'm wondering about what you didn't post. In btnClear_Click, do you set the mode before you start doing anything that would cause execute_timer_tick_stuff() to fail? Is there any other statement changing the value of mode (or _mode) that you didn't post? – jnm2 Jun 16 '11 at 21:18
  • No, that's the crazy thing, jnm2! 0. You are seeing the entirety of btnClear_Click. 1. _mode is only changed in the mode set method. 2. mode itself is changed by other event handlers that are not firing. And finally, I should have said that the mode (_mode) value *catches up* during the execution of the Tick event handler. In other words, Tick incorrectly sees mode = modetype.live and *then* catches mode = modetype.clear slightly later, somewhat randomly. – catfood Jun 16 '11 at 21:24
  • This is technically possible, code on the UI thread can be re-entrant through the message loop. Is the video capture component a COM based one? Do you use the lock keyword, WaitOne/Any or Thread.Join in your code? – Hans Passant Jun 16 '11 at 22:12
  • The fact that `mode` *catches up* during the timer tick event handler makes me think that something else is setting it. Have you placed a breakpoint in the `mode` setter to verify that nothing's calling it during the timer tick? Another possibility, although somewhat remote, is that you need to use a `MemoryBarrier` to ensure that the `_mode` value is actually written. See http://stackoverflow.com/questions/1095623/volatile-equivalent-in-vb-net for an example. – Jim Mischel Jun 17 '11 at 03:20
  • Hans, it seems that my _mode value may be changing during the call to the COM-based video capture component. That shouldn't be relevant because we're still in a WinForms event handler, right? Recall that this is Windows.Forms.Timer, so we respect the Windows message loop. – catfood Jun 17 '11 at 21:18
  • @catfood: Have you tried the code I suggested yet? – jnm2 Jun 18 '11 at 12:58
  • Jim Mischel has the right answer. There was a call to Application.DoEvents() buried a few levels deep in the call stack of the timer tick event. That was allowing the value of (mode) to change. Jim, if you want to enter that as an answer, I will mark it "best." Thanks to all who helped me eliminate other possibilities! Sorry the question turned out to be less interesting that originally thought. – catfood Jul 15 '11 at 03:00

1 Answers1

1

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
jnm2
  • 7,960
  • 5
  • 61
  • 99
  • In the end, this didn't actually solve my problem... but it's a good answer, and the only official answer, so enjoy some reputation points. Thanks, jnm2! – catfood Aug 23 '11 at 18:43