如何优雅地停止System.Threading.Timer?

时间:2012-09-10 15:32:03

标签: c# .net multithreading

我有一个用C#实现的Windows服务,需要经常做一些工作。我使用带有回调方法的System.Threading.Timer实现了这一点,该方法负责安排下一个回调。我无法正常停止(即处理)计时器。这是一些简化的代码,您可以在控制台应用程序中运行,以说明我的问题:

const int tickInterval = 1000; // one second

timer = new Timer( state => {
                       // simulate some work that takes ten seconds
                       Thread.Sleep( tickInterval * 10 );

                       // when the work is done, schedule the next callback in one second
                       timer.Change( tickInterval, Timeout.Infinite );
                   },
                   null,
                   tickInterval, // first callback in one second
                   Timeout.Infinite );

// simulate the Windows Service happily running for a while before the user tells it to stop
Thread.Sleep( tickInterval * 3 );

// try to gracefully dispose the timer while a callback is in progress
var waitHandle = new ManualResetEvent( false );
timer.Dispose( waitHandle );
waitHandle.WaitOne();

问题是,当ObjectDisposedException阻止时,我在回调线程上从timer.Change获得waitHandle.WaitOne。我做错了什么?

The documentation我正在使用的Dispose重载说:

  

在所有当前排队的回调完成之前,不会释放计时器。

编辑:来自文档的此声明似乎可能不正确。有人可以验证吗?

我知道我可以通过在回调和处理代码之间添加一些信号来解决这个问题,正如Henk Holterman在下面提到的那样,但除非绝对必要,否则我不想这样做。

4 个答案:

答案 0 :(得分:11)

使用此代码

 timer = new Timer( state => {
                   // simulate some work that takes ten seconds
                   Thread.Sleep( tickInterval * 10 );

                   // when the work is done, schedule the next callback in one second
                   timer.Change( tickInterval, Timeout.Infinite );
               },
               null,
               tickInterval, // first callback in one second
               Timeout.Infinite );

几乎可以肯定的是,你会在睡眠时处理掉计时器。

您必须在Sleep()之后保护代码以检测Disposed计时器。由于没有IsDisposed属性,快速而脏的static bool stopping = false;可能会成功。

答案 1 :(得分:0)

保护回调方法不使用已处理的计时器的可能解决方案:

https://stackoverflow.com/a/15902261/193178

答案 2 :(得分:0)

如" Windows上的并发编程"中所述 创建一个虚拟类InvalidWaitHandle,继承自WaitHandle:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Threading;

namespace MyNameSpace
{
    class InvalidWaitHandle : WaitHandle
    {

    }
}

因此,您可以像这样正确配置System.Threading.Timer:

public static void DisposeTimer()
{
   MyTimer.Dispose(new InvalidWaitHandle());
   MyTimer = null;
}

答案 3 :(得分:-4)

您无需丢弃计时器即可将其停止。您可以拨打Timer.Stop()或将Timer.Enabled设置为false,其中任何一个都会停止计时器的运行。