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

时间:2010-05-19 14:57:03

标签: c# reference timer dispose

根据[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);
    }

我认为没有提到计时器,到目前为止我没有看到任何问题,但这可能是因为使用的延迟时间非常小。

上面的代码是错误的吗?如果是,如何保持对Timer的引用?我认为这样的事情可能有用:

    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 个答案:

答案 0 :(得分:1)

更新

更普遍地考虑你的问题,我认为你实际上想要实现的目标是以更简单的方式实现,而根本不使用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指定非零值会导致action执行两次?


原始答案

timerstate课程确实没有必要。只需将System.Threading.Timer成员添加到包含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)。这意味着您希望计时器在action结束后拨打ms 一次;我对吗?如果是这种情况,那么实际上并不需要担心计时器被处理(即,它会是,并且没关系),假设ms的值相对较低。

无论如何,如果您要保留IDisposable对象的实例(例如System.Threading.Timer),则通常应该在 对象时处置该成员(即,这个实例)被处置掉。我相信System.Threading.Timer有一个终结器,最终会导致它被处理掉,但最好在你不再需要它时立即处理它们。所以:

public class Delayer : IDisposable
{
    // same code as above, plus...

    public void Dispose()
    {
        _timer.Dispose();
    }
}

答案 1 :(得分:1)

你的第二种方法也不会保留参考。在Delay2-block结束之后,对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)

我从您的评论中读到了现有的答案,你可以拥有0..n动作,所以你也会有0..n计时器。是对的吗?在这种情况下,您应该执行以下操作之一:

  1. 保留计时器列表/字典,但在这种情况下,您必须在触发后删除计时器。
  2. 构建一个调度程序:拥有1个定时器,定时触发,为每个延迟调用添加动作一个计算时间,它应该运行到List / Dictionary,每次定时器触发,检查列表并运行&amp; remove那个行动。您甚至可以构建此调度程序,它按执行时间对Actions进行排序,并将Timer设置为足够的间隔。

答案 3 :(得分:1)

“工作”代码确实是不确定性垃圾回收/终结器的副作用。

此代码作为C#语句在LINQ Pad中运行,显示了此问题-由于计时器已被GC处理(并且调用了终结器并清除了内部计时器,因此将记录消息)资源..

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

但是,请注释掉“ GC.Collect”语句,并且消息将记录2秒钟,因为在程序结束之前没有[立即]执行垃圾回收器 finalizer ,因此未立即调用垃圾回收。

由于行为是不确定的,因此也应将其视为依赖于错误的错误:}

后续代码中存在相同的问题,因为需要强引用来确保对象未进行GC处理-在该示例中,仍然没有对{{1 }}包装器对象,因此存在相同的问题,尽管存在一个更高级别的间接访问。