C# 可靠地停止System.Threading.Timer?

C# 可靠地停止System.Threading.Timer?,c#,multithreading,timer,C#,Multithreading,Timer,我已经找了很多方法来解决这个问题。我正在寻找一种干净、简单的方法来防止在我停止System.Threading.Timer的回调方法后调用它 我似乎找不到,这让我有时求助于可怕的thread-thread.sleep-thread.abort组合 可以使用lock完成吗?建议您使用Dispose(WaitHandle)方法停止计时器,并在不再调用回调时得到通知。也许您应该做相反的操作。使用system.timers.timer,将自动重置设置为false,并仅在需要时启动它您不能保证应该停止计时

我已经找了很多方法来解决这个问题。我正在寻找一种干净、简单的方法来防止在我停止System.Threading.Timer的回调方法后调用它

我似乎找不到,这让我有时求助于可怕的thread-thread.sleep-thread.abort组合


可以使用lock完成吗?

建议您使用
Dispose(WaitHandle)
方法停止计时器,并在不再调用回调时得到通知。

也许您应该做相反的操作。使用system.timers.timer,将自动重置设置为false,并仅在需要时启动它

您不能保证应该停止计时器的代码将在计时器事件调用之前执行。 例如,假设在时间点0,您初始化了计时器,以便在时间点5到来时调用事件。然后在时间点3,你决定不再需要这个电话。并调用了您要在此处编写的方法。然后,当方法是JIT ted时,时间点4出现,操作系统决定线程耗尽其时间片并切换。无论您如何尝试,计时器都将调用该事件-您的代码在最坏的情况下根本没有机会运行

这就是为什么在事件处理程序中提供一些逻辑更安全。可能是一些ManualResetEvent,当您不再需要事件调用时,它将被重置。因此,您需要处理计时器,然后设置ManualResetEvent。在计时器事件处理程序中,您要做的第一件事是TestManualResetEvent。如果它处于重置状态-请立即返回。因此,您可以有效地防止某些代码的意外执行。

like建议您应该使用
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();
}

不管它值多少钱,我们经常使用这种模式:

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

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

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

一个更简单的解决方案可能是将
计时器设置为永不恢复;该方法可以获取
dueTime
period
的值,指示计时器永不重新启动:


虽然改用可能是一个“更好”的解决方案,但总会有不实用的时候;仅使用就足够了。

对于System.Threading.Timer,可以执行以下操作(还可以保护回调方法不使用disposed Timer-ObjectDisposedException):


您可以通过创建如下类并从回调方法调用它来停止计时器:

public class InvalidWaitHandle : WaitHandle
{
    public IntPtr Handle
    {
        get { return InvalidHandle; }
        set { throw new InvalidOperationException(); }
    }
}
if (_secondsElapsed > 80)
{
    _t.Dispose(new InvalidWaitHandle());
}
正在实例化计时器:

_t = new Timer(DisplayTimerCallback, TBlockTimerDisplay, 0, 1000);
然后内部回调方法:

public class InvalidWaitHandle : WaitHandle
{
    public IntPtr Handle
    {
        get { return InvalidHandle; }
        set { throw new InvalidOperationException(); }
    }
}
if (_secondsElapsed > 80)
{
    _t.Dispose(new InvalidWaitHandle());
}

有一个MSDN链接如何正确实现停止计时器。将
ControlThreadProc()
方法与
handleepersed(对象发送方,ElapsedEventArgs e)
事件通过
syncPoint
静态类变量同步。注释掉
Thread.Sleep(testRunsFor)ControlThreadProc()
上选择code>。 关键是在条件语句上使用静态变量和原子操作,如
Interlocked.compareeexchange

链接:
对我来说,这似乎是正确的方法: 使用完计时器后,只需调用
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);
    }
}
此答案与System.Threading.Timer有关 我在网上读了很多关于如何同步处理
System.Threading.Timer
的废话。所以,这就是为什么我发布这篇文章,试图在某种程度上纠正这种情况。如果我写的东西有问题,请随时告诉我/打电话给我;-)

陷阱 在我看来,存在以下陷阱:

  • Timer.Dispose(WaitHandle)
    可以返回false。它这样做是为了防止它已经被处理掉(我必须查看源代码)。在这种情况下,它不会设置
    WaitHandle
    ——所以不要等待它
  • 未处理
    WaitHandle
    超时。说真的-如果你对超时不感兴趣,你还在等什么
  • 在处置期间(而非之后)可能发生
    ObjectDisposedException
    的并发问题
  • Timer.Dispose(WaitHandle)
    不能与-
    Slim
    waithandles一起正常工作,或者不像人们期望的那样。例如,以下操作不起作用(它会永远阻塞):
解决方案 我想标题有点“粗体”,但下面是我处理这个问题的尝试——一个处理双重处理、超时和
ObjectDisposedException
的包装器。不过,它并没有提供计时器上的所有方法,但可以随意添加它们

internal class Timer
{
    private readonly TimeSpan _disposalTimeout;

    private readonly System.Threading.Timer _timer;

    private bool _disposeEnded;

    public Timer(TimeSpan disposalTimeout)
    {
        _disposalTimeout = disposalTimeout;
        _timer = new System.Threading.Timer(HandleTimerElapsed);
    }

    public event Signal Elapsed;

    public void TriggerOnceIn(TimeSpan time)
    {
        try
        {
            _timer.Change(time, Timeout.InfiniteTimeSpan);
        }
        catch (ObjectDisposedException)
        {
            // race condition with Dispose can cause trigger to be called when underlying
            // timer is being disposed - and a change will fail in this case.
            // see 
            // https://msdn.microsoft.com/en-us/library/b97tkt95(v=vs.110).aspx#Anchor_2
            if (_disposeEnded)
            {
                // we still want to throw the exception in case someone really tries
                // to change the timer after disposal has finished
                // of course there's a slight race condition here where we might not
                // throw even though disposal is already done.
                // since the offending code would most likely already be "failing"
                // unreliably i personally can live with increasing the
                // "unreliable failure" time-window slightly
                throw;
            }
        }
    }

    private void HandleTimerElapsed(object state)
    {
        Elapsed.SafeInvoke();
    }

    public void Dispose()
    {
        using (var waitHandle = new ManualResetEvent(false))
        {
            // returns false on second dispose
            if (_timer.Dispose(waitHandle))
            {
                if (!waitHandle.WaitOne(_disposalTimeout))
                {
                    throw new TimeoutException(
                        "Timeout waiting for timer to stop. (...)");
                }
                _disposeEnded = true;
            }
        }
    }
}

事后不是你真正的问题。很难在回调运行时停止它。这是很有可能的。使用锁。虽然不是什么大问题,但还是应该检查和处理。否则回调可能会尝试使用一些已释放的资源,或者假设它们从未被再次调用等。我多次看到应用程序退出时由于回调中的错误假设而导致的奇怪错误。为什么不提出一个自包含的解决方案呢?自包含的解决方案存在,它被称为计时器。我的建议是,既然有可能错误地使用计时器,为什么不创建一个在这方面不能错误使用的可重用类呢。这个类可以包含复杂性,因此您不必担心它。事实上,我认为几乎任何同步工具都不会被错误地使用。除了计时器,我不会尝试提出解决方案,因为作为一个通用工具,它非常简单。为什么要让事情变得更复杂、更容易出错?我认为它不会简化任何事情。这不是线程安全的,因为(1)计时器=null可以从线程更新,回调线程仍然可以看到它的旧值。(2) 如果在计时器内使用计时器实例,请在“if(timer!=null)”检查后回调;可能是b
 using(var manualResetEventSlim = new ManualResetEventSlim)
 {
     timer.Dispose(manualResetEventSlim.WaitHandle);
     manualResetEventSlim.Wait();
 }
internal class Timer
{
    private readonly TimeSpan _disposalTimeout;

    private readonly System.Threading.Timer _timer;

    private bool _disposeEnded;

    public Timer(TimeSpan disposalTimeout)
    {
        _disposalTimeout = disposalTimeout;
        _timer = new System.Threading.Timer(HandleTimerElapsed);
    }

    public event Signal Elapsed;

    public void TriggerOnceIn(TimeSpan time)
    {
        try
        {
            _timer.Change(time, Timeout.InfiniteTimeSpan);
        }
        catch (ObjectDisposedException)
        {
            // race condition with Dispose can cause trigger to be called when underlying
            // timer is being disposed - and a change will fail in this case.
            // see 
            // https://msdn.microsoft.com/en-us/library/b97tkt95(v=vs.110).aspx#Anchor_2
            if (_disposeEnded)
            {
                // we still want to throw the exception in case someone really tries
                // to change the timer after disposal has finished
                // of course there's a slight race condition here where we might not
                // throw even though disposal is already done.
                // since the offending code would most likely already be "failing"
                // unreliably i personally can live with increasing the
                // "unreliable failure" time-window slightly
                throw;
            }
        }
    }

    private void HandleTimerElapsed(object state)
    {
        Elapsed.SafeInvoke();
    }

    public void Dispose()
    {
        using (var waitHandle = new ManualResetEvent(false))
        {
            // returns false on second dispose
            if (_timer.Dispose(waitHandle))
            {
                if (!waitHandle.WaitOne(_disposalTimeout))
                {
                    throw new TimeoutException(
                        "Timeout waiting for timer to stop. (...)");
                }
                _disposeEnded = true;
            }
        }
    }
}