Какие ссылки создает экземпляр System.Timers.Timer
в моем приложении, если я создаю/подписываюсь и запускаю его?
Вот ссылки, о которых я знаю:
- Ссылка на экземпляр нашего таймера, который мы храним в некоторой переменной.
- Неявная ссылка, когда мы подписываемся на событие
Elapsed
Я предполагаю, что должны быть созданы какие-то дополнительные ссылки, когда мы Start
запускаем наш таймер. Но что это такое?
Вот фрагмент кода, с которым я экспериментировал:
class Program
{
static void Main(string[] args)
{
var timers = new List<Timer>();
Console.WriteLine("Timers to create/start and subscribe: ");
// step 1
var count = 1000000;
for (int i = 0; i < count; i++)
{
var timer = new Timer(100000);
timers.Add(timer);
// step 2
timer.Elapsed += Callback;
timer.Start();
}
// step 3: 7mb -> 240mb
Console.WriteLine("All timers subscribed and started");
Task.Delay(TimeSpan.FromSeconds(15)).Wait();
// step 4
timers.ForEach(t => t.Elapsed -= Callback);
Console.WriteLine("All timers unsubscribed");
// step 5: uncomment next two lines to have timers GC collected.
// timers.ForEach(t => t.Stop());
// Console.WriteLine("All timers stopped");
// step 6
timers.Clear();
Console.WriteLine("Collection cleared");
// step 7
do
{
Console.WriteLine("Press Enter to GC.Collect");
Console.ReadLine();
GC.Collect();
Console.WriteLine("GC Collected");
}
while (true);
}
private static void Callback(object sender, ElapsedEventArgs e)
{
Console.WriteLine("callback");
}
}
Вот что я делаю здесь:
- Создайте миллион экземпляров
Timer
и сохраните их в списке. - Для каждого таймера: подпишитесь на событие
Elapsed
, передав ему ссылку на статический метод, а затем вызовитеStart
. - Как только это будет сделано, объем памяти моего приложения увеличится с 7 МБ до 240 МБ.
- После этого я отписываюсь от всех событий
Elapsed
таймеров, тем самым убивая неявную ссылку на подписку на события. - НЕОБЯЗАТЕЛЬНО: Остановите все таймеры.
- Очистить список таймеров, чтобы удалить ссылки на их экземпляры.
- GC.Collect для освобождения памяти, выделенной для таймеров.
Если выполняется Step 5
(таймеры остановлены), то на GC.Collect
шаге я вижу, что память, занимаемая моим приложением, уменьшается с 240 МБ до примерно 20 МБ, что означает, что таймеры были перезапущены правильно.
Если Step 5
опущено, то на GC.Collect
шаге использование памяти остается на уровне 240 МБ независимо от того, как долго я жду и принудительно собираю сборщик мусора. Что фактически означает, что экземпляры таймера остаются в памяти и не могут быть собраны.
Из того, что я знаю, как работает GC, мои таймеры должны быть очищены из памяти, если они недоступны из корня (имеется в виду отсутствие ссылок).
Есть ли другие ссылки на мои таймеры, которые я могу удалить без явного вызова timer.Stop()
?
Есть ли другой способ предотвратить утечку памяти, не вызывая метод Stop
?