Problem with Timer and AutoResetEvent
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:
Quote:
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!