All,

My apologies for the length of this posting.

I am trying to write code that uses a timer to execute code periodically. The code that executes can take upwards of 0.5 seconds to execute and the period can be as short as 1.0 seconds. The user has the ability, via a button, to start/stop the process. The challenge is that if the block of code in the timer is running when the stop button is clicked, that block of code must be allowed to finish executing.

The following code is a simplified version of my program (which exhibits the same, undesirable behavior, see more below):

Code:
Imports System.Text
Imports System.Threading

Public Class fWaitOne
    Dim DeltaTime As Integer
    Dim Generator As System.Random = New System.Random()

    Private Sub txbDeltaTime_Leave(sender As Object, e As System.EventArgs) Handles txbDeltaTime.Leave
        DeltaTime = 1000 * CInt(txbDeltaTime.Text)
    End Sub

    Dim safeToKill As AutoResetEvent
    Dim myTimer As Threading.Timer

    Private Sub btnStartStop_Click(sender As System.Object, e As System.EventArgs) Handles btnStartStop.Click
        Static isRunning As Boolean = False

        If Not isRunning Then ' start timer
            isRunning = True
            btnExit.Enabled = False
            btnStartStop.Text = "Click to Stop"

            safeToKill = New AutoResetEvent(False)
            myTimer = New Threading.Timer(AddressOf MyTimer_Tick, Nothing, 0, DeltaTime)
        Else ' kill timer
            safeToKill.WaitOne(DeltaTime) ' ** line 26 **
            myTimer.Change(Timeout.Infinite, Timeout.Infinite) ' ** line 27 **
            myTimer.Dispose()
            myTimer = Nothing
            safeToKill.Dispose()

            isRunning = False
            txbMsg.Text = ""
            btnExit.Enabled = True
            btnStartStop.Text = "Click to Start"
        End If
    End Sub

    Private Sub MyTimer_Tick(ByVal state As Object)
        safeToKill.Reset() ' only allow the thread to be killed when not processing
        myTimer.Change(Timeout.Infinite, Timeout.Infinite)

        Dim bTime As DateTime = Now
        Dim iTime As Integer = Generator.Next(500, 600) ' simulate the process
        Thread.Sleep(iTime)

        DisplayMessage(txbMsg, "Time: " + Format(bTime, "mm:ss.fff") + ", Loop delay: " + iTime.ToString + vbCrLf)

        Dim waitTime As Integer = 0
        Dim exeTime As TimeSpan = Now.Subtract(bTime)
        Dim tMSec As Integer = exeTime.Milliseconds
        If DeltaTime > tMSec Then
            waitTime = DeltaTime - tMSec ' set wait so time between ticks is DeltaTime
        End If

        myTimer.Change(waitTime, DeltaTime)
        safeToKill.Set()
    End Sub

    Private Sub btnExit_Click(sender As System.Object, e As System.EventArgs) Handles btnExit.Click
        Me.Close()
    End Sub

    Private Delegate Sub AppendTextBoxDelegate(ByVal TB As TextBox, ByVal txt As String)

    Private Sub DisplayMessage(ByVal TB As TextBox, ByVal txt As String)
        If TB.InvokeRequired Then
            TB.Invoke(New AppendTextBoxDelegate(AddressOf DisplayMessage), New Object() {TB, txt})
        Else
            TB.AppendText(txt)
        End If
    End Sub

End Class
If you run this program, as is, and periodically click btnStartStop, you will eventually be greeted with the following error:

System.ObjectDisposedException was unhandled
HResult=-2146232798
Message=Safe handle has been closed
ObjectName=""
Source=mscorlib
StackTrace:
at System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean& success)
at System.StubHelpers.StubHelpers.SafeHandleAddRef(SafeHandle pHandle, Boolean& success)
at Microsoft.Win32.Win32Native.SetEvent(SafeWaitHandle handle)
at System.Threading.EventWaitHandle.Set()
at WaitOneTest.fWaitOne.MyTimer_Tick(Object state) in C:\Users\Gerry\Documents\Visual Studio 2008\Projects\Learning and Testing\WaitOneTest\WaitOneTest\fWaitOne.vb:line 59
at System.Threading.TimerQueueTimer.CallCallbackInContext(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.TimerQueueTimer.CallCallback()
at System.Threading.TimerQueueTimer.Fire()
at System.Threading.TimerQueue.FireNextTimers()
at System.Threading.TimerQueue.AppDomainTimerCallback()
InnerException:
If you comment out Line 27, it is easier to trigger this error.

If you remove DeltaTime from Line 26, the program will, often, but not always, hang because the WaitOne is never satisfied.

If you increase the value for 'Time between readings', it is harder to trigger either of these problems.

I suspect that a race condition occurs when the click happens at just the right time. However, I have been beating on this for hours and cannot figure out where the mistake is.

Please help!