简体   繁体   English

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

[英]System.Threading.Timer keep reference to it

According to [ http://msdn.microsoft.com/en-us/library/system.threading.timer.aspx][1] you need to keep a reference to a System.Threading.Timer to prevent it from being disposed. 根据[ http://msdn.microsoft.com/zh-cn/library/system.threading.timer.aspx][1],您需要保留对System.Threading.Timer的引用以防止其被处置。

I've got a method like this: 我有这样的方法:

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);
    }

Which I don't think keeps a reference to the timer, I've not seen any problems so far, but that's probably because the delay periods used have been pretty small. 我不认为这是对计时器的引用,到目前为止,我还没有发现任何问题,但这可能是因为使用的延迟时间非常短。

Is the code above wrong? 上面的代码错误吗? And if it is, how to I keep a reference to the Timer? 如果是的话,如何保存对计时器的引用? I'm thinking something like this might work: 我在想这样的事情可能会起作用:

    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);
        }

The locking business is so I can get the timer into the timerstate class before the delegate gets invoked. 锁定事务是这样的,因此我可以在调用委托之前使计时器进入timerstate类。 It all looks a little clunky to me. 在我看来,这一切都有些笨拙。 Perhaps I should regard the chance of the timer firing before it's finished constructing and assigned to the property in the timerstace instance as negligible and leave the locking out. 也许我应该认为在计时器构造完成并在timerstace实例中分配给该属性之前,计时器触发的机会可以忽略不计,而不必进行锁定。

Update 更新

Thinking about your problem a bit more generally, I think what you're actually trying to accomplish here is achievable in a much simpler way, without using a System.Threading.Timer at all. 从更一般的角度考虑问题,我认为您实际上要在此处完成的任务可以以一种非常简单的方式实现,而无需使用System.Threading.Timer

Is this basically what you want your method to do? 这基本上就是您想要您的方法执行的吗? Perform action after a specified number of milliseconds? 在指定的毫秒数后执行action If so, I would suggest something like the following alternative implementation instead: 如果是这样,我建议改用以下替代实现:

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);
}

...by the way, are you aware that in the code you posted, specifying a non-zero value for ms will cause action to be executed twice? ...顺便说一句,您是否知道在发布的代码中,为ms指定非零值将导致action执行两次?


Original Answer 原始答案

The timerstate class really isn't necessary. timerstate类确实不是必需的。 Just add a System.Threading.Timer member to whatever class contains your Delay method; 只需将System.Threading.Timer成员添加到包含Delay方法的任何类中即可; then your code should look like this: 那么您的代码应如下所示:

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);
    }
}

Now, I see that you are specifying the period argument of the timer's constructor as System.Threading.Timeout.Infinite (-1). 现在,我看到您将计时器的构造函数的period参数指定为System.Threading.Timeout.Infinite (-1)。 What this means is that you intend for your timer to call action once , after ms has elapsed; 这意味着您打算让计时器在ms过去后立即调用action am I right? 我对吗? If this is the case, then there's actually not much need to worry about the timer being disposed anyway (ie, it will be, and that's fine), assuming a relatively low value for ms . 如果是这种情况,那么实际上就不需要担心计时器会被丢弃了(即,它将是这样,这很好),假设ms值相对较低。

Anyway, if you're going to hold onto an instance of an IDisposable object (like System.Threading.Timer ), you should generally dispose of that member when your object (ie, this instance) is disposed of. 无论如何,如果你要守住一个实例IDisposable对象(如System.Threading.Timer ),你通常应该在你的对象(即,这种情况下)设置的处置该成员。 I believe System.Threading.Timer has a finalizer that will cause it to be disposed of eventually anyway, but it's best to dispose of things as soon as you don't need them anymore. 我相信System.Threading.Timer具有终结器,无论如何最终都会将其丢弃,但是最好不要再使用它们了。 So: 所以:

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

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

Your second approach wouldn't keep the reference either. 您的第二种方法也不会保留参考。 After the end of the Delay2-block, the reference to state is gone so the Garbage Collector will collect it ... then your reference to Timer is gone, too and it will be collected and disposed. 在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);   
    }   
}

I read from your comments to the existing answers that you can have 0..n Actions and so you would have 0..n Timers, too. 从您对现有答案的评论中可以看出,您可以使用0..n动作,因此您也可以使用0..n计时器。 Is that right? 那正确吗? In this case you should do one of the following: 在这种情况下,您应该执行以下任一操作:

  1. Keep a List/Dictionary of timers, but in this case you have to remove the timer after firing. 保留计时器列表/词典,但是在这种情况下,您必须在触发后删除计时器。
  2. Build a scheduler: have 1 Timer, that fired regulary, for each Delay-call add the action an the calculated time when it should run into a List/Dictionary, each time the timer fired, check you list and run&remove the Action. 构建一个调度程序:有1个定期触发的计时器,对于每个“延迟”调用,请为该动作添加一个计算的时间,该时间应在该事件运行到“列表/字典”中时,每次触发该计时器时,请检查您列出的内容并运行并删除该动作。 You can even build this scheduler that it sorts the Actions by execution time an sets the Timer to an adequate interval. 您甚至可以构建此调度程序,以便按执行时间对操作进行排序,并将计时器设置为足够的时间间隔。

The code "working" is indeed a side-effect of a non-deterministic Garbage Collection / finalizers . 代码“工作”确实是非确定性垃圾回收/终结器的副作用。

This code, running in LINQ Pad as C# Statements, shows the issue - no messages will be logged because the Timer is GC'ed (and the finalizer is called and it cleans up the internal timer resources..) 这段代码在LINQ Pad中作为C#语句运行,显示了此问题-由于Timer进行了GC处理,因此不会记录任何消息(并且调用了finalizer并清理了内部计时器资源。)

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

However, comment out the "GC.Collect" statement and messages will be logged for 2 seconds as Garbage Collection is not [immediately] performed the Timer's finalizer is not called prior to the program ending. 但是,注释掉“ GC.Collect”语句,消息将被记录2秒钟,因为没有(立即)执行垃圾回收,在程序结束之前未调用Timer的终结器。

Since the behavior is non-deterministic , it should also be considered a bug to rely on :} 由于行为是不确定的 ,因此也应将其视为依赖于以下行为的错误:}

The same issue exists in the follow on code because a strong reference is required to ensure an object is not GC'ed - in that example, there is still no reference kept to the timer wrapper object so the same issue exists, albeit with one more level of indirection.. 后续代码中存在相同的问题,因为需要强引用来确保对象没有被GC处理-在该示例中, timer包装对象仍然没有引用,因此存在相同的问题,尽管还有一个间接级别

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM