How to keep a System.Timers.Timer stopped during ElapsedEvent

652 views Asked by At

I am using System.Timers.Timer and every x seconds I need to perform some tasks in an ElapsedEvent method. While I am performing my tasks in the ElapsedEvent method, I want the timer to be stopped. However, I have another method that can start the timer, which can be called while the ElapsedEvent is running. My code looks something like this:

class MyClass {
   Timer myTimer;

   public MyClass {
      myTimer = new System.Timers.Timer();
      // init timer code here...
   }

   public void ElapsedEventTask(object source, ElapsedEventArgs e) {
      myTimer.Enabled = false;
      try
      {
          // do my tasks
      }
      catch
      {
         ...
      }
      finally
      {
         myTimer.Enabled = true;
      }
   }
}

public void AnotherMethod() {
   // do some things
   myTimer.Enabled = true;
}

How do I prevent AnotherMethod from starting the timer while I'm completing the task in ElapsedEventTask?

3

There are 3 answers

3
Theodor Zoulias On

According to the documentation the System.Timers.Timer class is not thread-safe, so it's not safe to touch its Enabled property from multiple threads without synchronization (doing so results to undefined behavior). Vernou's answer shows how to synchronize the threads by using locks, but personally I am a bit nervous with trying to enforce a non-overlapping execution policy using a mechanism that apparently was designed to be re-entrant. So my suggestion is to ditch the System.Timers.Timer, and use instead an asynchronous loop, controlled by Stephen Cleary's PauseTokenSource mechanism:

class MyClass
{
    private readonly CancellationTokenSource _cts;
    private readonly PauseTokenSource _pts;
    public Task Completion { get; private set; }

    public MyClass(TimeSpan interval)
    {
        _cts = new CancellationTokenSource();
        _pts = new PauseTokenSource();
        _pts.IsPaused = true;
        Completion = Task.Run(async () =>
        {
            try
            {
                while (true)
                {
                    await _pts.Token.WaitWhilePausedAsync(_cts.Token);
                    var delayTask = Task.Delay(interval, _cts.Token);
                    /* Do my tasks */
                    await delayTask;
                }
            }
            catch (OperationCanceledException)
                when (_cts.IsCancellationRequested) { } // Ignore
        });
    }

    public void Start() => _pts.IsPaused = false;
    public void Stop() => _pts.IsPaused = true;
    public void Complete() => _cts.Cancel();
}

The PauseTokenSource is the controller of a PauseToken, a similar concept with the CancellationTokenSource/CancellationToken combo. The difference is that the CancellationTokenSource can be canceled only once, while the PauseTokenSource can be paused/unpaused multiple times. This class is included in the AsyncEx.Coordination package.

The MyClass exposes a Complete method that terminates the asynchronous loop, and a Completion property that can be awaited. It is a good idea to await this property before closing the program, to give to any active operation the chance to complete. Otherwise the process may be killed in the middle of a background execution, with unpredictable consequences.

0
Neil On

I would create a one shot timer, which you then need to start again at the end of your timer function.

myTimer = new System.Timers.Timer();
myTimer.AutoReset = false;


public void ElapsedEventTask(object source, ElapsedEventArgs e) {
  ...
  finally
  {
     myTimer.Start();
  }

}

2
vernou On

You can add a variable that indicate if the task is running. Finaly to be thread safe, you need to use lock when this variable is used in with myTimer.Enabled :

class MyClass
{
    object syncEnableRunning = new object();
    bool running
    Timer myTimer;

    public void ElapsedEventTask(object source, ElapsedEventArgs e)
    {
        lock(syncEnableRunning)
        {
            running = true;
            myTimer.Enabled = false;
        }
        
        try { /*do my tasks*/}
        catch { ... }
        finally
        {
            lock(syncEnableRunning)
            {
                myTimer.Enabled = true;
                running = false;
            }
        }
    }

    public void AnotherMethod()
    {
        // do some things
        lock(syncEnableRunning)
        {
            if(!running)
            {
                myTimer.Enabled = true;
            }
        }
    }
}