75 votes

Arrêter de manière fiable System.Threading.Timer ?

J'ai beaucoup cherché une solution à ce problème. Je cherche un moyen propre et simple d'empêcher la méthode de rappel d'un System.Threading.Timer d'être invoquée après que je l'ai arrêté.

Je n'arrive pas à en trouver, ce qui m'a conduit, à l'occasion, à recourir à la redoutable combinaison thread-thread.sleep-thread.abort.

Peut-on le faire en utilisant la serrure ?

149voto

Owen Blacker Points 1781

Une solution plus simple pourrait être de définir l'option Timer pour ne jamais reprendre ; la méthode Timer.Change peut prendre des valeurs pour dueTime y period qui demandent au minuteur de ne jamais redémarrer :

this.Timer.Change(Timeout.Infinite, Timeout.Infinite);

Tout en changeant pour utiliser System.Timers.Timer pourrait être une "meilleure" solution, il y aura toujours des moments où cela ne sera pas pratique ; il suffit d'utiliser Timeout.Infinite devrait suffire.

44voto

Jalal Aldeen Saa'd Points 9120

Comme Conrad Frix a suggéré d'utiliser le System.Timers.Timer à la place, comme :

private System.Timers.Timer _timer = new System.Timers.Timer();
private volatile bool _requestStop = false;

public constructor()
{
    _timer.Interval = 100;
    _timer.Elapsed += OnTimerElapsed;
    _timer.AutoReset = false;
    _timer.Start();
}

private void OnTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    // do work....
    if (!_requestStop)
    {
        _timer.Start();//restart the timer
    }
}

private void Stop()
{
    _requestStop = true;
    _timer.Stop();
}

private void Start()
{
    _requestStop = false;
    _timer.Start();
}

14voto

Will A Points 16763

El Docs MSDN vous suggère d'utiliser le Dispose(WaitHandle) pour arrêter le timer + être informé quand les callbacks ne seront plus invoqués.

12voto

Rolf Kristensen Points 1326

Pour le System.Threading.Timer, on peut faire ce qui suit (cela empêchera également la méthode de rappel de fonctionner sur un timer disposé - ObjectDisposedException) :

class TimerHelper : IDisposable
{
    private System.Threading.Timer _timer;
    private readonly object _threadLock = new object();

    public event Action<Timer,object> TimerEvent;

    public void Start(TimeSpan timerInterval, bool triggerAtStart = false,
        object state = null)
    {
        Stop();
        _timer = new System.Threading.Timer(Timer_Elapsed, state,
            System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);

        if (triggerAtStart)
        {
            _timer.Change(TimeSpan.FromTicks(0), timerInterval);
        }
        else
        {
            _timer.Change(timerInterval, timerInterval);
        }
    }

    public void Stop(TimeSpan timeout = TimeSpan.FromMinutes(2))
    {
        // Wait for timer queue to be emptied, before we continue
        // (Timer threads should have left the callback method given)
        // - http://woowaabob.blogspot.dk/2010/05/properly-disposing-systemthreadingtimer.html
        // - http://blogs.msdn.com/b/danielvl/archive/2011/02/18/disposing-system-threading-timer.aspx
        lock (_threadLock)
        {
            if (_timer != null)
            {
                ManualResetEvent waitHandle = new ManualResetEvent(false)
                if (_timer.Dispose(waitHandle))
                {
                   // Timer has not been disposed by someone else
                   if (!waitHandle.WaitOne(timeout))
                      throw new TimeoutException("Timeout waiting for timer to stop");
                }
                waitHandle.Close();   // Only close if Dispose has completed succesful
                _timer = null;
            }
        }
    }

    public void Dispose()
    {
        Stop();
        TimerEvent = null;
    }

    void Timer_Elapsed(object state)
    {
        // Ensure that we don't have multiple timers active at the same time
        // - Also prevents ObjectDisposedException when using Timer-object
        //   inside this method
        // - Maybe consider to use _timer.Change(interval, Timeout.Infinite)
        //   (AutoReset = false)
        if (Monitor.TryEnter(_threadLock))
        {
            try
            {
                if (_timer==null)
                    return;

                Action<Timer, object> timerEvent = TimerEvent;
                if (timerEvent != null)
                {
                    timerEvent(_timer, state);
                }
            }
            finally
            {
                Monitor.Exit(_threadLock);
            }
        }
    }
}

Voici comment on peut l'utiliser :

void StartTimer()
{
    TimerHelper _timerHelper = new TimerHelper();
    _timerHelper.TimerEvent += (timer,state) => Timer_Elapsed();
    _timerHelper.Start(TimeSpan.FromSeconds(5));
    System.Threading.Sleep(TimeSpan.FromSeconds(12));
    _timerHelper.Stop();
}

void Timer_Elapsed()
{
   // Do what you want to do
}

6voto

Pour ce que ça vaut, nous utilisons ce modèle assez souvent :

// set up timer
Timer timer = new Timer(...);
...

// stop timer
timer.Dispose();
timer = null;
...

// timer callback
{
  if (timer != null)
  {
    ..
  }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X