可靠地停止System.Threading.Timer?

时间:2011-06-16 22:56:21

标签: c# multithreading timer

我已经搜索了很多解决方案。我正在寻找一种干净简单的方法来防止在我停止后调用System.Threading.Timer的回调方法。

我似乎无法找到任何东西,这导致我在某个时候采用可怕的线程 - thread.sleep-thread.abort组合颤抖

可以使用锁来完成吗?请帮我找到一个好办法。感谢

11 个答案:

答案 0 :(得分:102)

更简单的解决方案可能是设置Timer永远不会恢复;方法Timer.Change可以获取dueTimeperiod的值,指示计时器永远不会重新启动:

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

虽然改为使用System.Timers.Timer可能是一个“更好”的解决方案,但总会有时候这种方法不实用;只需使用Timeout.Infinite即可。

答案 1 :(得分:40)

Conrad Frix建议您应该使用System.Timers.Timer类,例如:

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();
}

答案 2 :(得分:10)

对于System.Threading.Timer,可以执行以下操作(还可以保护回调方法不会处理已处理的计时器 - 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);
            }
        }
    }
}

这是人们可以使用它的方式:

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
}

答案 3 :(得分:7)

MSDN Docs建议您使用Dispose(WaitHandle)方法停止计时器+在不再调用回调时通知。

答案 4 :(得分:6)

对于它的价值,我们使用这种模式:

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

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

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

答案 5 :(得分:3)

对我来说,这似乎是正确的方法: 完成计时器后,只需致电dispose即可。这将停止计时器并防止将来的预定呼叫。

见下面的例子。

class Program
{
    static void Main(string[] args)
    {
        WriteOneEverySecond w = new WriteOneEverySecond();
        w.ScheduleInBackground();
        Console.ReadKey();
        w.StopTimer();
        Console.ReadKey();
    }
}

class WriteOneEverySecond
{
    private Timer myTimer;

    public void StopTimer()
    {
        myTimer.Dispose();
        myTimer = null;
    }

    public void ScheduleInBackground()
    {
        myTimer = new Timer(RunJob, null, 1000, 1000);
    }

    public void RunJob(object state)
    {
        Console.WriteLine("Timer Fired at: " + DateTime.Now);
    }
}

答案 6 :(得分:2)

这个答案与System.Threading.Timer

有关

我已经阅读了很多关于如何在网络上同步处理System.Threading.Timer的废话。所以这就是我发布这个以试图纠正这种情况的原因。如果我写的东西错了,请随时告诉我/叫我; - )

陷阱

在我看来,存在这些陷阱:

  • Timer.Dispose(WaitHandle)可以返回false。它是这样做的,以防它已被处理(我必须查看源代码)。在这种情况下,不会设置WaitHandle - 所以不要等待它!
  • 未处理WaitHandle超时。说真的 - 如果你对超时不感兴趣,还等什么呢?
  • 提及的并发问题here on msdn,其中{/ 1}}可以在(而不是之后)处理期间发生
  • ObjectDisposedException与 - Timer.Dispose(WaitHandle)等待处理无法正常工作,或者与人们的期望不同。例如,以下工作(它永远阻止):
Slim

解决方案

我猜这个标题有点“大胆”,但下面是我尝试处理这个问题 - 一个处理双重处理,超时和 using(var manualResetEventSlim = new ManualResetEventSlim) { timer.Dispose(manualResetEventSlim.WaitHandle); manualResetEventSlim.Wait(); } 的包装器。它虽然没有提供ObjectDisposedException上的所有方法,但可以随意添加它们。

Timer

答案 7 :(得分:1)

也许你应该做相反的事情。使用system.timers.timer,将AutoReset设置为false,仅在需要时启动它

答案 8 :(得分:1)

您无法保证在定时器事件调用之前,应该停止计时器的代码将执行。 例如,假设在时刻0时你初始化定时器以在时刻5到来时调用事件。然后在第3时刻,您决定不再需要通话。你想在这里写一个叫做方法。然后当方法被JIT-ted来到时刻4并且OS决定你的线程耗尽其时间片并切换。无论你如何尝试,计时器都会调用该事件 - 你的代码在最坏的情况下就没有机会运行。

这就是为什么在事件处理程序中提供一些逻辑更安全的原因。也许有些ManualResetEvent会在您不再需要事件调用时立即重置。因此,您将处理计时器,然后设置ManualResetEvent。在timer事件处理程序中,首先要做的是测试ManualResetEvent。如果它处于复位状态 - 只需立即返回。因此,您可以有效地防止意外执行某些代码。

答案 9 :(得分:0)

你可以通过创建这样的类并从例如你的回调方法调用它来停止计时器:

public class InvalidWaitHandle : WaitHandle
{
    public IntPtr Handle
    {
        get { return InvalidHandle; }
        set { throw new InvalidOperationException(); }
    }
}

实例化计时器:

_t = new Timer(DisplayTimerCallback, TBlockTimerDisplay, 0, 1000);

然后在回调方法中:

if (_secondsElapsed > 80)
{
    _t.Dispose(new InvalidWaitHandle());
}

答案 10 :(得分:0)

有一个MSDN链接如何正确实现停止计时器。使用ControlThreadProc()方法将HandleElapsed(object sender, ElapsedEventArgs e)事件与syncPoint静态类变量同步。如果Thread.Sleep(testRunsFor);不适合(可能),请在ControlThreadProc()上注释掉。 关键在于使用静态变量和条件语句上的Interlocked.CompareExchange之类的原子操作。

链接: Timer.Stop Method