代码之家  ›  专栏  ›  技术社区  ›  Daniel James Bryars

System.Threading.Timer保留对它的引用

  •  2
  • Daniel James Bryars  · 技术社区  · 14 年前

    根据[ http://msdn.microsoft.com/en-us/library/system.threading.timer.aspx][1] 您需要保留对System.Threading.Timer的引用,以防止其被释放。

    我有这样一种方法:

    private void Delay(Action action, Int32 ms)
        {
            if (ms <= 0)
            {
                action();
            }
    
            System.Threading.Timer timer = new System.Threading.Timer(
                (o) => action(), 
                null, 
                ms, 
                System.Threading.Timeout.Infinite);
        }
    

    我不认为它会保留计时器的引用,到目前为止我还没有看到任何问题,但那可能是因为使用的延迟时间很短。

    上面的代码错了吗?如果是的话,我该如何保存计时器的引用?我在想这样的办法可能管用:

        class timerstate 
        {
            internal volatile System.Threading.Timer Timer;
        };
    
        private void Delay2(Action action, Int32 ms)
        {
            if (ms <= 0)
            {
                action();
            }
    
    
            timerstate state = new timerstate();
            lock (state)
            {
                state.Timer = new System.Threading.Timer(
                    (o) => 
                    { 
                        lock (o) 
                        { 
                            action();
                            ((timerstate)o).Timer.Dispose();
                        } 
                    },
                    state,
                    ms,
                    System.Threading.Timeout.Infinite);
            }
    

    锁定业务是这样的,我可以在调用委托之前将计时器放入timerstate类中。我觉得这一切都有点笨拙。也许我应该把计时器在构造完成并分配给timerstace实例中的属性之前触发的机会看作是微不足道的,而不考虑锁定。

    4 回复  |  直到 14 年前
        1
  •  1
  •   Hinek    14 年前

    你的第二种方法也不会保留参考。在Delay2块结束后,引用 state 已经不见了所以垃圾收集员会收集它。。。那么你提到的 Timer

    class MyClass
    {
        private System.Threading.Timer timer;
    
        private void Delay(Action action, Int32 ms)   
        {   
            if (ms <= 0)   
            {   
                action();   
            }   
    
            timer = new System.Threading.Timer(   
                (o) => action(),    
                null,    
                ms,    
                System.Threading.Timeout.Infinite);   
        }   
    }
    
        2
  •  1
  •   Dan Tao    14 年前

    更新

    更一般地考虑一下你的问题,我认为你在这里真正想要实现的是一个简单得多的方法,而不是使用 System.Threading.Timer 完全。

    这基本上就是你想要的方法吗?执行 action 在指定的毫秒数之后?如果是这样的话,我建议使用以下替代实现:

    private void Delay(Action action, int ms)
    {
        if (ms <= 0)
        {
            action();
            return;
        }
    
        System.Threading.WaitCallback delayed = state =>
        {
            System.Threading.Thread.Sleep(ms);
            action();
        };
    
        System.Threading.ThreadPool.QueueUserWorkItem(delayed);
    }
    

    …顺便问一下,您是否知道在您发布的代码中,为 ms 行动


    原始答案

    这个 timerstate 上课真的没必要。只需添加一个 系统线程计时器 任何包含您的 Delay 方法;则代码应如下所示:

    public class Delayer
    {
        private System.Threading.Timer _timer;
    
        private void Delay(Action action, Int32 ms)
        {
            if (ms <= 0)
            {
                action();
            }
    
            _timer = new System.Threading.Timer(
                (o) => action(), 
                null, 
                ms, 
                System.Threading.Timeout.Infinite);
        }
    }
    

    现在,我看到您正在指定 period System.Threading.Timeout.Infinite (-1)。这意味着你打算让你的计时器 一旦 质谱 已经过去了;我说得对吗?如果是这样的话,那么实际上不必担心计时器会被释放(也就是说,它会被释放,这很好),假设 .

    不管怎样,如果你想抓住一个 IDisposable 对象(如 系统线程计时器 你的 对象(即,此实例)被释放。我相信 系统线程计时器

    public class Delayer : IDisposable
    {
        // same code as above, plus...
    
        public void Dispose()
        {
            _timer.Dispose();
        }
    }
    
        3
  •  1
  •   Hinek    14 年前

    我从你的评论中读到现有的答案,你可以有0…N动作,所以你也会有0…N定时器。对吗?在这种情况下,应执行以下操作之一:

    1. 保留计时器的列表/字典,但在这种情况下,必须在启动后删除计时器。
        4
  •  1
  •   user2864740    6 年前

    代码“工作”确实是 非确定性垃圾收集/终结器 .

    这段代码在LINQ Pad中作为C#语句运行,显示了问题所在- 消息将被记录,因为计时器是GC'ed的(调用终结器并清理内部计时器资源…)

    new System.Threading.Timer((o) => { "Hi".Dump(); }, this, 100, 100);
    GC.Collect();
    Thread.Sleep(2000);
    

    但是,注释掉“GC.Collect”语句,消息将被记录2秒,因为垃圾收集不会[立即]执行计时器的 终结器 在程序结束之前不调用。

    不确定性 ,它也应该被视为一个要依赖的bug:}

    在后续代码中存在同样的问题,因为 强引用 是确保对象不是GC'ed所必需的-在该示例中,仍然没有保留对 timer 包装对象因此存在相同的问题,尽管还有一个间接的级别。