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):
If you run this program, as is, and periodically click btnStartStop, you will eventually be greeted with the following error: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 comment out Line 27, it is easier to trigger this 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 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!




Reply With Quote
