我需要创建一堆计时器作为局部变量来执行以下操作:
void Foo()
{
Timer t = new Timer(myTimerCallback,null,1000,Timeout.Infinite);
}
[不幸的是,其中一些是由GC收集的,它们在1秒后调用myTimerCallback。由于我有很多计时器,因此无法将它们存储在私有静态成员中。而且也很难找到放置GC.Keeplive(t)的地方。
我如何让每个计时器在死前去做自己的事情?
您可以将它们存储在集合中以保留引用,并在事件方法中从集合中删除引用。我认为您需要使用状态参数来标识集合中的计时器(将对计时器的引用或集合中的键作为“新计时器”语句中的状态参数传递)。
请注意,回调函数是在其他线程上执行的,因此您将/可能同时运行多个回调函数。使用锁定来确保添加/删除引用安全地进行。
您最好的选择是将它们存储在成员变量中,然后在不再需要它们时将其处理。
将它们存储在成员中,例如List<Timer>
。
为什么无法存储它们?也许您可以在完成工作后将其从该集合中删除?
重要的是要确保GC.KeepAlive()无法解决您的问题。这只是将对象的生命周期延长到语句。只要计时器需要保持活动状态,您就必须循环或阻止。还要注意,局部变量中引用的生命周期在调试版本中是不同的。 JIT可以使它们保持生命,直到该方法可以使用手表为止。产生棘手的“在调试中起作用,在发行模式下不起作用”的问题。
您必须保留对计时器对象的寿命参考。这通常是通过类中的字段完成的。生存期要求现在传递给了类对象,只要需要计时器,它就必须保持生存状态。通常不是问题。如果是这样,则必须将引用设为静态。
这不是解决您的问题的方法,但我认为它仍然与主题相关。
System.Threading.Timer
的Timer(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();
}
}