必须填写至少10个字的日志
nnhy 编写于 2012-07-27 18:48:21
X
using System;
using System.Collections.Generic;
using System.Threading;
using NewLife.Log;

namespace NewLife.Threading
{
    /// <summary>不可重入的定时器。</summary>
    /// <remarks>
    /// 为了避免系统的Timer可重入的问题,差别在于本地调用完成后才开始计算时间间隔。这实际上也是经常用到的。
    /// 
    /// 因为挂载在静态列表上,必须从外部主动调用<see cref="IDisposable.Dispose"/>才能销毁定时器。
    /// 
    /// 该定时器不能放入太多任务,否则适得其反!
    /// 
    /// TimerX必须维持对象,否则很容易被GC回收。
    /// </remarks>
    public class TimerX : DisposeBase
    {
        #region 属性
        private WeakAction<Object> _Callback;
        /// <summary>回调</summary>
        public WeakAction<Object> Callback { get { return _Callback; } set { _Callback = value; } }

        private Object _State;
        /// <summary>用户数据</summary>
        public Object State { get { return _State; } set { _State = value; } }

        private DateTime _NextTime;
        /// <summary>下一次调用时间</summary>
        public DateTime NextTime { get { return _NextTime; } set { _NextTime = value; } }

        private Int32 _Timers;
        /// <summary>调用次数</summary>
        public Int32 Timers { get { return _Timers; } set { _Timers = value; } }

        private Int32 _Period;
        /// <summary>间隔周期</summary>
        public Int32 Period { get { return _Period; } set { _Period = value; } }

        private Boolean _UseThreadPool;
        /// <summary>是否使用线程池。对于耗时短小且比较频繁的操作,不好使用线程池,减少线程切换。</summary>
        public Boolean UseThreadPool { get { return _UseThreadPool; } set { _UseThreadPool = value; } }

        private Boolean _Calling;
        /// <summary>调用中</summary>
        public Boolean Calling { get { return _Calling; } set { _Calling = value; } }
        #endregion

        #region 构造
        /// <summary>实例化一个不可重入的定时器</summary>
        /// <param name="callback">委托</param>
        /// <param name="state">用户数据</param>
        /// <param name="dueTime">多久之后开始</param>
        /// <param name="period">间隔周期</param>
        public TimerX(WaitCallback callback, object state, int dueTime, int period) : this(callback, state, dueTime, period, period > 10000) { }

        /// <summary>实例化一个不可重入的定时器</summary>
        /// <param name="callback">委托</param>
        /// <param name="state">用户数据</param>
        /// <param name="dueTime">多久之后开始</param>
        /// <param name="period">间隔周期</param>
        /// <param name="usethreadpool">是否使用线程池。对于耗时短小且比较频繁的操作,不好使用线程池,减少线程切换。</param>
        public TimerX(WaitCallback callback, object state, int dueTime, int period, Boolean usethreadpool)
        {
            if (callback == null) throw new ArgumentNullException("callback");
            if (dueTime < Timeout.Infinite) throw new ArgumentOutOfRangeException("dueTime");
            if (period < Timeout.Infinite) throw new ArgumentOutOfRangeException("period");

            Callback = new WeakAction<object>(callback);
            State = state;
            Period = period;
            UseThreadPool = usethreadpool;

            NextTime = DateTime.Now.AddMilliseconds(dueTime);

            TimerXHelper.Add(this);
        }
        #endregion

        #region 辅助
        /// <summary>已重载</summary>
        /// <returns></returns>
        public override string ToString()
        {
            return Callback != null ? "" + Callback : base.ToString();
        }
        #endregion

        #region 设置
        private static Boolean _Debug;
        /// <summary>是否开启调试,输出更多信息</summary>
        public static Boolean Debug { get { return _Debug; } set { _Debug = value; } }

        static void WriteLog(String format, params Object[] args)
        {
            if (Debug) XTrace.WriteLine(format, args);
        }
        #endregion

        #region 内部助手
        static class TimerXHelper
        {
            static Thread thread;

            static List<TimerX> timers = new List<TimerX>();

            public static void Add(TimerX timer)
            {
                lock (timers)
                {
                    timers.Add(timer);

                    WriteLog("TimerX.Add {0}ms {1}", timer.Period, timer);

                    timer.OnDisposed += new EventHandler(delegate(Object sender, EventArgs e)
                    {
                        var tx = sender as TimerX;
                        if (tx == null) return;
                        lock (timers)
                        {
                            WriteLog("TimerX.Remove {0}", tx);

                            if (timers.Contains(tx)) timers.Remove(tx);
                        }
                    });

                    if (thread == null)
                    {
                        thread = new Thread(Process);
                        //thread.Name = "TimerX";
                        thread.Name = "T";
                        thread.IsBackground = true;
                        thread.Start();
                    }

                    if (waitForTimer != null && waitForTimer.SafeWaitHandle != null && !waitForTimer.SafeWaitHandle.IsClosed) waitForTimer.Set();
                }
            }

            static AutoResetEvent waitForTimer;
            static Int32 period = 10;

            static void Process(Object state)
            {
                while (true)
                {
                    try
                    {
                        var list = Prepare();

                        // 记录本次循环有几个任务被处理
                        //Int32 count = 0;
                        // 设置一个较大的间隔,内部会根据处理情况调整该值为最合理值
                        period = 60000;
                        foreach (TimerX timer in list)
                        {
                            ProcessItem(timer);
                        }
                    }
                    catch (ThreadAbortException) { break; }
                    catch (ThreadInterruptedException) { break; }
                    catch { }

                    //Thread.Sleep(period);
                    if (waitForTimer == null) waitForTimer = new AutoResetEvent(false);
                    waitForTimer.WaitOne(period, false);
                }
            }

            static List<TimerX> Prepare()
            {
                if (timers == null || timers.Count < 1)
                {
                    // 没有计时器,设置一个较大的休眠时间
                    //period = 60000;

                    // 使用事件量来控制线程
                    if (waitForTimer != null) waitForTimer.Close();

                    waitForTimer = new AutoResetEvent(false);
                    waitForTimer.WaitOne(Timeout.Infinite, false);
                }

                List<TimerX> list = null;
                lock (timers)
                {
                    list = new List<TimerX>(timers);
                }

                return list;
            }

            static void ProcessItem(TimerX timer)
            {
                // 删除过期的
                if (!timer.Callback.IsAlive)
                {
                    lock (timers)
                    {
                        timers.Remove(timer);
                        timer.Dispose();
                    }
                    return;
                }

                TimeSpan ts = timer.NextTime - DateTime.Now;
                Int32 d = (Int32)ts.TotalMilliseconds;
                if (d > 0)
                {
                    // 缩小间隔,便于快速调用
                    if (d < period) period = d;

                    return;
                }

                WriteLog("TimerX.Process {0}", timer);

                try
                {
                    timer.Calling = true;

                    Action<Object> callback = timer.Callback;
                    // 线程池调用
                    if (timer.UseThreadPool)
                        ThreadPoolX.QueueUserWorkItem(delegate() { callback(timer.State); });
                    else
                        callback(timer.State);
                }
                catch (ThreadAbortException) { throw; }
                catch (ThreadInterruptedException) { throw; }
                catch { }
                finally
                {
                    timer.Timers++;
                    timer.Calling = false;
                    timer.NextTime = DateTime.Now.AddMilliseconds(timer.Period);
                    if (timer.Period < period) period = timer.Period;
                }
            }
        }
        #endregion
    }
}