Reputation: 5660
I'm using a Timer class that wraps CreateTimerQueueTimer
and DeleteTimerQueueTimer
.
Here is the class:
using System;
using System.Threading;
using MyCompany.Internal;
using TimerCallback = MyCompany.Internal.TimerCallback;
public class Timer : IDisposable
{
public Timer()
{
this.callback = this.ticked;
this.autoReset = true;
Computer.ChangeTimerResolutionTo(1);
this.priority = ThreadPriority.Normal;
}
public virtual event EventHandler Elapsed;
public virtual bool AutoReset
{
get
{
return this.autoReset;
}
set
{
this.autoReset = value;
}
}
public virtual ThreadPriority Priority
{
get
{
return this.priority;
}
set
{
this.priority = value;
}
}
public virtual void Start(int interval)
{
if (interval < 1)
{
throw new ArgumentOutOfRangeException("interval", "Interval must be at least 1 millisecond.");
}
if (Interlocked.CompareExchange(ref this.started, 1, 0) == 1)
{
return;
}
NativeMethods.CreateTimerQueueTimer(
out this.handle,
IntPtr.Zero,
this.callback,
IntPtr.Zero,
(uint)interval,
(uint)interval,
CallbackOptions.ExecuteInTimerThread);
}
public virtual void Stop()
{
if (Interlocked.CompareExchange(ref this.started, 0, 1) == 0)
{
return;
}
NativeMethods.DeleteTimerQueueTimer(IntPtr.Zero, this.handle, IntPtr.Zero);
}
public virtual void Dispose()
{
this.Stop();
}
private void ticked(IntPtr parameterPointer, bool unused)
{
if (!this.AutoReset)
{
this.Stop();
}
Thread.CurrentThread.Priority = this.Priority;
var elapsed = this.Elapsed;
if (elapsed != null)
{
elapsed(this, EventArgs.Empty);
}
}
private int started;
private IntPtr handle;
private volatile bool autoReset;
private ThreadPriority priority;
private readonly TimerCallback callback;
}
The problem is, after awhile I'm getting an SEHException when calling Start and Stop simultaneously from multiple threads. The Interlocked.CompareExchange
methods should prevent DeleteTimerQueueTimer from being called once after Stop()
is called, right? Even if Stop()
is called simultaneously from different threads?
The SEHException
is being thrown at DeleteTimerQueueTimer()
; I assume it's because it's trying to delete a timer that has already been deleted, making the handle invalid. Doesn't the CompareExchange
prevent DeleteTimerQueueTimer
from being called more than once, even by multiple threads simultaneously?
Upvotes: 1
Views: 201
Reputation: 6050
The function Interlocked.CompareExchange prevent the variable 'started' from modified at same time from 2 threads, but the handle of the timer is the real one you want to protected, but the code fails to do in some cases.
For example, thread A call the start function, it execute the function Interlocked.CompareExchange and then this.started is 1; at this time thread A call the stop function, it sees that 'started' is one, so it will call function DeleteTimerQueueTimer to delete the timer, while the timer might not be created yet and the handle is invalid.
So you should protect the handle of the timer
Upvotes: 1