The Cog
The Cog

Reputation: 173

Using a timer in a Windows service - elapsed event handler keeps firing even when the timer is disabled

I've read a lot of the posts related to Windows services and the use of timers, but I haven't found any reason why my event handler is still being triggered. Could anyone point me in the right direction? I want to know why this is happening so I understand how to avoid this in the future.

Edit: The onError event handler is never invoked (or I would have seen the event in the event log).

Timer: System.Timers.Timer
ServiceBase: System.ServiceProcess.ServiceBase


Here is the abstract class:

public abstract class ABCService : ServiceBase
{
    // Members
    protected Timer             invocationTimer;
    protected Timer             wellnessTimer;
    protected FileSystemWatcher fsw;


    // Constructors
    protected ABCService()
    {
        invocationTimer = new Timer();
        wellnessTimer   = new Timer();
        fsw             = null;

        invocationTimer.AutoReset = false;
        invocationTimer.Interval  = 30000; // 30 seconds
        invocationTimer.Elapsed  += new ElapsedEventHandler(invocationTimer_Elapsed);

        wellnessTimer.AutoReset   = false;
        wellnessTimer.Elapsed    += new ElapsedEventHandler(wellnessTimer_Elapsed);
    }


    // Methods
    protected void invocationTimer_Elapsed(object o, ElapsedEventArgs args)
    {
        try
        {
            // log to event log

            invocationTimer.Stop();

            if ((new FileInfo(fsw.Path + "\\" + fsw.Filter)).Exists)
            {
                onCreated(this, new FileSystemEventArgs(WatcherChangeTypes.Created, fsw.Path, fsw.Filter));
            }
        }
        catch (Exception x)
        {
            onError(this, new ErrorEventArgs(x));
        }
    }

    protected void wellnessTimer_Elapsed(object o, ElapsedEventArgs args)
    {
        try
        {
            // log to event log

            wellnessTimer.Stop();
            wellnessTimer.Interval = 60000; // ms

            if (fsw != null)
            {
                fsw.Dispose();
            }

            fsw = new FileSystemWatcher(ConfigurationManager.AppSettings["pathKey"], ConfigurationManager.AppSettings["filterKey"]);

            invocationTimer.Start();
        }
        catch (Exception x)
        {
            onError(this, new ErrorEventArgs(x));
        }
    }

    protected abstract void onCreated(object o, FileSystemEventArgs args);

    protected virtual void onError(object o, ErrorEventArgs args)
    {
        // log to event log

        wellnessTimer.Start();
    }

    protected override void OnStart(string[] args)
    {
        // log to event log

        wellnessTimer.Interval = 5000; // 5 seconds
        wellnessTimer.Start();
    }

    protected override void OnStop()
    {
        // log to event log

        wellnessTimer.Stop();
    }
}

Here is an instance class:

public partial class Service1 : ABCService
{
    // Members
    private static object locket = new object();
    private static DateTime LAST_RUN_TIME = DateTime.Now.AddSeconds(-10);


    // Constructors
    public Service1()
    {
        InitializeComponent();
    }


    // Methods
    protected override void onCreated(object o, FileSystemEventArgs args)
    {
        lock (locket)
        {
            // log to event log

            if ((DateTime.Now - LAST_RUN_TIME).Seconds >= 10)
            {
                // do stuff
            }
            else
            {
                // log to event log

                invocationTimer.Stop();
                invocationTimer.Start();
            }
        }
    }
}

Here is the autogenerated code for the partial class:

partial class Service1
{
    /// <summary> 
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Component Designer generated code

    /// <summary> 
    /// Required method for Designer support - do not modify 
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
        // 
        // Service1
        // 
        this.ServiceName = "Service1";

    }

    #endregion
}

So what exactly is happening? I'm looking at my event log and I see that once every minute the wellnessTimer event handler is invoked.

Here is what I think is happening, but I'm obviously wrong:

1. Service is started via MMC
2. OnStart() method is invoked
3. wellnessTimer interval is set to 5 seconds
4. wellnessTimer start method is invoked
5. wellnessTimer_Elapsed event handler is invoked
6. wellnessTimer stop method is invoked
7. wellnessTimer interval is set to 5 minutes
8. invocationTimer start method is invoked
9. 30 seconds later, the invocationTimer_Elapsed method is invoked
10. invocationTimer stop method is invoked

At this point both timers should still exist for this instance, but should be disabled. I debugged this via attach to process in Visual Studio 2010 and marked an ID for the object (sender) being passed into the event handlers. It is the same object as the instance. Also, both timers in the Locals window had their enabled property set to false.

This makes me thing I'm using inheritance incorrectly, or something is going on with threading. I'm not the best at either of those things, but if it is due to them please let me know so I can learn.

Thanks to everyone in advance.


Edit #2: Here is some trace data...

'o' represents the object passed into the event handler

ABCService() method invoked <--
ABCService() method invoked -->

Service1() method invoked <--
Service1() method invoked -->

OnStart() method invoked <--
OnStart() method invoked -->

wellnessTimer_Elapsed() method invoked <--
((System.Timers.Timer) o).Enabled  = False
((System.Timers.Timer) o).Interval = 5000
this.wellnessTimer.Enabled  = False
this.wellnessTimer.Interval = 5000
wellnessTimer_Elapsed() method invoked -->

invocationTimer_Elapsed() method invoked <--
((System.Timers.Timer) o).Enabled  = False
((System.Timers.Timer) o).Interval = 30000
this.invocationTimer.Enabled  = False
this.invocationTimer.Interval = 30000
invocationTimer_Elapsed() method invoked -->

wellnessTimer_Elapsed() method invoked <--
((System.Timers.Timer) o).Enabled  = False
((System.Timers.Timer) o).Interval = 60000
this.wellnessTimer.Enabled  = False
this.wellnessTimer.Interval = 60000
wellnessTimer_Elapsed() method invoked -->

invocationTimer_Elapsed() method invoked <--
((System.Timers.Timer) o).Enabled  = False
((System.Timers.Timer) o).Interval = 30000
this.invocationTimer.Enabled  = False
this.invocationTimer.Interval = 30000
invocationTimer_Elapsed() method invoked -->

Upvotes: 1

Views: 15682

Answers (2)

Steve Py
Steve Py

Reputation: 34908

Are you experiencing the problem with the code exactly as you provided, or would it be dependent on code around:

       // depending on logic, these next two lines will be called together or not at all

Based on what I read from the code, the wellness timer will fire every 60 seconds when it encounters an error on the onCreated call. If this is doing file operations, the one thing that screams out is security permissions for the account the service is running as.

When catching an exception, Output it to the event log or a log file. Behaviour by exception can easily lead to confusing situations, especially where it's not so convenient to hook up a debugger.

Upvotes: 0

Thomas C. G. de Vilhena
Thomas C. G. de Vilhena

Reputation: 14595

From MSDN's remarks on the Timer class:

Elapsed events can occur after the Dispose or Stop method has been called or after the Enabled property has been set to false, because the signal to raise the Elapsed event is always queued for execution on a thread pool thread. One way to resolve this race condition is to set a flag that tells the event handler for the Elapsed event to ignore subsequent events.

So you can do something like this inside your event handler to prevent executing it after the timer has been disabled:

private static void OnTimedEvent(object source, ElapsedEventArgs e)
{
    if (timer.Enabled)
    {
        Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
    }
}

Upvotes: 1

Related Questions