定时器

目录

概念

  • System.Threading.Timer,可以定时在线程池上开启一个线程调用指定的方法。以下两种情况,在线程池上可能会有多个线程同时执行回调方法:一是计时器间隔小于执行此回调所需的时间;二是线程池资源紧张,回调方法的线程被多次排队未来得及执行。
  • 当我们不再需要执行定时任务时,可以利用Change( )暂停计时器,也可以调用Dispose()或将指向Timer的引用置为null。
  • 对Timer的引用必须一直存在,否则Timer会垃圾回收,间接调用了Dispose( )从而结束回调。
  • 当不再使用Timer时,必须调用其Dispose(),来回收其持有的非委托资源。
  • 调用Timer的Dispose()后,线程池可能残留有回调线程,这些线程在Dispose()后仍旧会执行。我们可以利用Dispose(WaitHandle) 实现释放资源后,并等待定时器启动的回调线程都执行完毕后,才执行Dispose(WaitHandle) 后的代码。
  • 调用Timer的Dispose()后,再调用Timer的其他成员,会抛出ObjectDisposedException异常。常见的情景,回调方法里面包含Timer的Change(),当Dispose()后,线程池的残留的线程会继续执行回调方法从而调用Change(),导致回调线程抛出ObjectDisposedException异常,我们在使用定时器时,应该妥善处理这种情况。
  • 如果Timer的回调方法耗时大于定时器的间隔时间,会出现多个线程同时执行回调方法的情况。我们可以避免这种情况,下面会讲述具体实现细节。
  • Timer类与系统时钟具有相同的分辨率。 这意味着,如果时间段小于系统时钟的分辨率,则 TimerCallback 委托将按系统时钟解析所定义的间隔(大约为 windows 7 和 windows 8 系统上的15毫秒)执行。

相关类

namespace System.Threading
{
    public static class Timeout
    {
        public const int Infinite = -1;
        public static readonly TimeSpan InfiniteTimeSpan;
    }
}
namespace System.Threading
{
    public delegate void TimerCallback(object state);
}

Timer

public sealed class Timer : MarshalByRefObject, IDisposable
{
    public Timer(TimerCallback callback);
    public Timer(TimerCallback callback, object state, int dueTime, int period);
    public Timer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period);
    public Timer(TimerCallback callback, object state, uint dueTime, uint period);
    public Timer(TimerCallback callback, object state, long dueTime, long period);
    public bool Change(int dueTime, int period);
    public bool Change(TimeSpan dueTime, TimeSpan period);
    public bool Change(uint dueTime, uint period);
    public bool Change(long dueTime, long period);
    public bool Dispose(WaitHandle notifyObject);
    public void Dispose();
}

callback

指定定时器每次触发时,执行的方法。

object state

通过这个参数为回调方法提供实参。定时器每次回调时,都会取最新的object参数的值。

dueTime

定时器第一次触发前等待的时长。如果该值为负值,则此计时器永远不会启动。如果该值为0,则计时器立刻触发第一次回调。如果为正数,则dueTime毫秒后,计时器触发第一次回调。

period

定时器每次触发回调的时间间隔。该值等于0或小于0时,则计时器只触发一次回调(dueTime >= 0)或一次都不触发(dueTime < 0)。该值大于0时,每隔period毫秒,会触发一次回调。

dueTime和period区别

  • 计数器启动后,dueTime毫秒后触发第一次回调,第一次回调后,每隔period毫秒后,触发一次回调。

  • 只有当dueTime不小于0时,Timer才可能执行回调。period若小于或等于0,计时器最多执行一次回调。

Timer使用方法

先构造后启用

  1. 构造Timer实例

    Timer timer = new Timer(new TimerCallback((state) =>{}));
    timer = new Timer(new TimerCallback((state) => { }), null, Timeout.Infinite, Timeout.Infinite);
    timer = new Timer(new TimerCallback((state) => { }), null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
    
  2. 使用Change()启动计时器

    timer.Change(100,1000);
    

构造时直接启动

Timer timer = new Timer(new TimerCallback((state) =>{}),null,100,1000);

我们可以利用Change()随时暂停,重启计时器,也可以改变计时器的第一次执行方法之前等待的时间量(截止时间)以及此后的执行期间等待的时间量(时间周期)。

避免多个线程执行回调方法

定时器会在回调方法执行完毕后,再过指定的时间,再进行下一次回调。

static void Main(string[] args)
{
    Timer timer = null;

    timer = new Timer((state) =>
    {
        Thread.Sleep(2000);
        Console.WriteLine("执行定时任务");
        timer.Change(1000, Timeout.Infinite);
    }, null, 1000, Timeout.Infinite);

    Console.ReadLine();
}

这个示例,会每隔3秒打印一次“执行定时任务”。

MSDN示例代码

using System;
using System.Threading;

class TimerExample
{
    static void Main()
    {
        // Create an AutoResetEvent to signal the timeout threshold in the
        // timer callback has been reached.
        var autoEvent = new AutoResetEvent(false);
        
        var statusChecker = new StatusChecker(10);

        // Create a timer that invokes CheckStatus after one second, 
        // and every 1/4 second thereafter.
        Console.WriteLine("{0:h:mm:ss.fff} Creating timer.\n", 
                          DateTime.Now);
        var stateTimer = new Timer(statusChecker.CheckStatus, 
                                   autoEvent, 1000, 250);

        // When autoEvent signals, change the period to every half second.
        autoEvent.WaitOne();
        stateTimer.Change(0, 500);
        Console.WriteLine("\nChanging period to .5 seconds.\n");

        // When autoEvent signals the second time, dispose of the timer.
        autoEvent.WaitOne();
        stateTimer.Dispose();
        Console.WriteLine("\nDestroying timer.");
    }
}

class StatusChecker
{
    private int invokeCount;
    private int  maxCount;

    public StatusChecker(int count)
    {
        invokeCount  = 0;
        maxCount = count;
    }

    // This method is called by the timer delegate.
    public void CheckStatus(Object stateInfo)
    {
        AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
        Console.WriteLine("{0} Checking status {1,2}.", 
            DateTime.Now.ToString("h:mm:ss.fff"), 
            (++invokeCount).ToString());

        if(invokeCount == maxCount)
        {
            // Reset the counter and signal the waiting thread.
            invokeCount = 0;
            autoEvent.Set();
        }
    }
}
// The example displays output like the following:
//       11:59:54.202 Creating timer.
//       
//       11:59:55.217 Checking status  1.
//       11:59:55.466 Checking status  2.
//       11:59:55.716 Checking status  3.
//       11:59:55.968 Checking status  4.
//       11:59:56.218 Checking status  5.
//       11:59:56.470 Checking status  6.
//       11:59:56.722 Checking status  7.
//       11:59:56.972 Checking status  8.
//       11:59:57.223 Checking status  9.
//       11:59:57.473 Checking status 10.
//       
//       Changing period to .5 seconds.
//       
//       11:59:57.474 Checking status  1.
//       11:59:57.976 Checking status  2.
//       11:59:58.476 Checking status  3.
//       11:59:58.977 Checking status  4.
//       11:59:59.477 Checking status  5.
//       11:59:59.977 Checking status  6.
//       12:00:00.478 Checking status  7.
//       12:00:00.980 Checking status  8.
//       12:00:01.481 Checking status  9.
//       12:00:01.981 Checking status 10.
//       
//       Destroying timer.

避免出现ObjectDisposeException异常

等待线程池所有的回调线程全部结束

当我们不想再执行定时任务时,如果不在意任务执行的次数,可以直接Dispose();如果在意的话,可以先利用Change( )暂停向线程池排队线程,然后利用Dispose(WaitHandle waitHandle)确定线程池里面所有的回调线程都执行完毕,再继续后续流程。

如果回调方法中调用了Timer的成员,可能会出现调用了一个对象的Dispose()后再次调用该对象的其他成员导致抛出异常的情况,对于Timer则是ObjectDisposeException,我们最好处理一下该可能的异常。

When a timer is no longer needed, use the Dispose method to free the resources held by the timer. Note that callbacks can occur after the Dispose() method overload has been called, because the timer queues callbacks for execution by thread pool threads. You can use the Dispose(WaitHandle) method overload to wait until all callbacks have completed.

The callback method executed by the timer should be reentrant, because it is called on ThreadPool threads. The callback can be executed simultaneously on two thread pool threads if the timer interval is less than the time required to execute the callback, or if all thread pool threads are in use and the callback is queued multiple times.

上一篇:How to resize an OCFS2 filesystem on Linux


下一篇:Checking out and building Chromium on Linux