如何防止我的Timer在回调被执行之前收集它?

问题描述 投票:0回答:6

我需要创建一堆计时器作为局部变量来执行以下操作:

void Foo()
{
    Timer t = new Timer(myTimerCallback,null,1000,Timeout.Infinite);
}

[不幸的是,其中一些是由GC收集的,它们在1秒后调用myTimerCallback。由于我有很多计时器,因此无法将它们存储在私有静态成员中。而且也很难找到放置GC.Keeplive(t)的地方。

我如何让每个计时器在死前去做自己的事情?

c#
6个回答
1
投票

您可以将它们存储在集合中以保留引用,并在事件方法中从集合中删除引用。我认为您需要使用状态参数来标识集合中的计时器(将对计时器的引用或集合中的键作为“新计时器”语句中的状态参数传递)。

请注意,回调函数是在其他线程上执行的,因此您将/可能同时运行多个回调函数。使用锁定来确保添加/删除引用安全地进行。


2
投票

您最好的选择是将它们存储在成员变量中,然后在不再需要它们时将其处理。


1
投票

将它们存储在成员中,例如List<Timer>


1
投票

为什么无法存储它们?也许您可以在完成工作后将其从该集合中删除?


1
投票

重要的是要确保GC.KeepAlive()无法解决您的问题。这只是将对象的生命周期延长到语句。只要计时器需要保持活动状态,您就必须循环或阻止。还要注意,局部变量中引用的生命周期在调试版本中是不同的。 JIT可以使它们保持生命,直到该方法可以使用手表为止。产生棘手的“在调试中起作用,在发行模式下不起作用”的问题。

您必须保留对计时器对象的寿命参考。这通常是通过类中的字段完成的。生存期要求现在传递给了类对象,只要需要计时器,它就必须保持生存状态。通常不是问题。如果是这样,则必须将引用设为静态。


0
投票

这不是解决您的问题的方法,但我认为它仍然与主题相关。

System.Threading.TimerTimer(TimerCallback callback)构造函数(不使用dueTime等的构造函数)将this用作state,这意味着计时器将保留对其自身的引用,这意味着它将在垃圾收集中幸存下来。

public Timer(TimerCallback callback)
{
    (...)
    TimerSetup(callback, this, (UInt32)dueTime, (UInt32)period, ref stackMark);
}

示例

计时器'C'是使用Timer(TimerCallback callback)创建的,并且一直沿用GC.Collect()

输出

A
B
C
A
B
C
GC Collected
B
C
C
B
B
C

代码

class TimerExperiment
{
    System.Threading.Timer timerB;

    public TimerExperiment()
    {
        StartTimer("A"); // Not keeping this timer
        timerB = StartTimer("B"); // Keeping this timer
        StartTimer2("C"); // Not keeping this timer
    }

    static System.Threading.Timer StartTimer(string name) {
        return new System.Threading.Timer(_ =>
        {
            Console.WriteLine($"{name}");
        }, null, dueTime: Delay(name), period: TimeSpan.FromSeconds(1));
    }

    static System.Threading.Timer StartTimer2(string name)
    {
        //Create the timer using the constructor which only takes the callback
        var t = new System.Threading.Timer( _ => Console.WriteLine($"{name}"));
        t.Change(dueTime: Delay(name), period: TimeSpan.FromSeconds(1));
        return t;
    }

    static TimeSpan Delay(string name) 
            => TimeSpan.FromMilliseconds(Convert.ToInt64(name[0])*10);

}

class Program
{
    static async Task Main(string[] args)
    {
        var withTimers = new TimerExperiment();

        await Task.Delay(TimeSpan.FromSeconds(2));
        GC.Collect();
        Console.WriteLine("GC Collected");
        Console.ReadLine();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.