一般情况下,定时器使用匿名函数(callback)作为回调,一旦函数内部发生错误,很难定位到问题来源。

解决思路是:在调用 AddTimer 时,对当前调用栈做一次快照,保存为字符串,作为 Timer 对象的成员。当定时器触发时捕获到异常,直接将快照输出,调试起来就方便多了。这套辅助机制在 release 发行版中可以去掉。

/*
                * Author:  caoshanshan
                * Email:   me@dreamyouxi.com
                code copy from https://git.oschina.net/dreamyouxi/XYGame;
                 */
                using UnityEngine;
                using System.Collections;
                using System.Collections.Generic;
                using ExtBase;
                
                public sealed class TimerQueue : Singleton<TimerQueue>
                {
                
                    /// <summary>
                    /// 添加一个真实时间定时器,返回函数调用即可 打断定时任务
                    /// </summary>
                    /// <param name="time_delay"></param>
                    /// <param name="cb"></param>
                    /// <param name="repeat_times">  <0 will be run forever</param>
                    public VoidFuncVoid AddTimer(float time_delay, VoidFuncNN cb, int repeat_times = 1, params object[] objs)
                    {
                        return this.AddTimer(time_delay, () => { cb(objs); }, repeat_times);
                    }
                    /// <summary>
                    /// 添加一个真实时间定时器,返回函数调用即可 打断定时任务
                    /// </summary>
                    /// <param name="time_delay"></param>
                    /// <param name="cb"></param>
                    /// <param name="repeat_times">  <0 will be run forever</param>
                    public VoidFuncVoid AddTimer(float time_delay, VoidFuncVoid cb, int repeat_times = 1)
                    {
                        if (repeat_times == 0) return null;// () => { };
                        Timer time = new Timer();
                        time.delay = time_delay;
                        time.cb = cb;
                        time.repeat_times = repeat_times;
                        //记录定时器添加的时候的快照栈信息,以 提供错误时 更多调试信息
                        System.Diagnostics.StackTrace st = new System.Diagnostics.StackTrace(true);
                
                        string ss = "\n";
                        for (int i = 0; i < st.FrameCount; i++)
                        {
                            var f = st.GetFrame(i);
                            string name = f.GetFileName();
                            if (name.Length > Application.dataPath.Length)
                            {
                                name = name.Substring(Application.dataPath.Length, name.Length - Application.dataPath.Length);
                            }
                            ss += "   " + f.GetMethod().ToString() + " at " + name + " (" + f.GetFileLineNumber() + ") \n";
                        }
                        time.stackInfo = ss;
                        time.Init();
                        list.Add(time);
                        return () =>
                        {
                            if (time != null && this.list.Contains(time))
                                time.SetInValid();
                        };
                    }
                
                    /// <summary>
                    /// unity engine tick
                    /// </summary>
                    public void Tick()
                    {
                        this.Tick(ref list);
                    }
                    private void Tick(ref List<TimerBase> list)
                    {
                        if (list.Count < 1)
                            return;
                
                        for (int i = 0; i < list.Count; i++)
                        {
                            if (list[i] == null)
                                continue;
                
                            TimerBase timer = list[i] as TimerBase;
                            timer.Tick();
                        }
                
                        for (int i = 0; i < list.Count; )
                        {
                            TimerBase b = list[i] as TimerBase;
                            if (b.IsInValid())
                            {
                                list.Remove(b);
                            }
                            else
                            {
                                ++i;
                            }
                        }
                    }
                    public void Clear()
                    {
                        this.list_ms.Clear();
                        this.list.Clear();
                    }
                    List<TimerBase> list_ms = new List<TimerBase>();
                    List<TimerBase> list = new List<TimerBase>();
                
                }
                
                
                class TimerBase : GAObject
                {
                    public string stackInfo = "";
                    public virtual void Tick()
                    {
                
                    }
                
                    public VoidFuncVoid cb = null;
                    public int repeat_times = 1;
                    protected int repeat_times_current = 0;
                }
                
                sealed class Timer : TimerBase
                {
                    public override void Tick()
                    {
                        if (this.IsInValid()) return;
                
                        if (current < delay)
                        {
                            current += Time.deltaTime;
                            return;
                        }
                        if (cb != null)
                        {
                            try
                            {
                                cb();
                            }
                            catch (System.Exception e)
                            {
                                Debug.LogError(e.Message);
                                Debug.LogError("add timer stackTrace:" + this.stackInfo);
                                Debug.LogError("call stackTrace:" + e.StackTrace);
                            }
                            cb = null;
                        }
                        current = 0.0f;
                        if (++repeat_times_current >= repeat_times && repeat_times >= 0)
                        {
                            this.SetInValid();
                        }
                    }
                
                    public override bool Init()
                    {
                        return true;
                    }
                    float current = 0.0f;
                    public float delay = 0.0f;
                
                }