72 votes

Pourquoi un System.Timers.Timer survit-il à GC mais pas à System.Threading.Timer?

Il semble que l' System.Timers.Timer des occurrences sont maintenues en vie par un mécanisme, mais System.Threading.Timer instances ne sont pas.

Exemple de programme, avec une périodiques System.Threading.Timer et auto-reset - System.Timers.Timer:

class Program
{
  static void Main(string[] args)
  {
    var timer1 = new System.Threading.Timer(
      _ => Console.WriteLine("Stayin alive (1)..."),
      null,
      0,
      400);

    var timer2 = new System.Timers.Timer
    {
      Interval = 400,
      AutoReset = true
    };
    timer2.Elapsed += (_, __) => Console.WriteLine("Stayin alive (2)...");
    timer2.Enabled = true;

    System.Threading.Thread.Sleep(2000);

    Console.WriteLine("Invoking GC.Collect...");
    GC.Collect();

    Console.ReadKey();
  }
}

Lorsque j'exécute ce programme (.NET 4.0 Client, la Libération, en dehors du débogueur), seule l' System.Threading.Timer GC ed:

Stayin alive (1)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Stayin alive (1)...
Stayin alive (2)...
Invoking GC.Collect...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...
Stayin alive (2)...

EDIT: j'ai accepté de John réponse ci-dessous, mais je voulais préciser un peu.

Lors de l'exécution de l'exemple de programme ci-dessus (avec un point d'arrêt à l' Sleep), voici l'état des objets en question et l' GCHandle tableau:

!dso
OS Thread Id: 0x838 (2104)
ESP/REG  Object   Name
0012F03C 00c2bee4 System.Object[]    (System.String[])
0012F040 00c2bfb0 System.Timers.Timer
0012F17C 00c2bee4 System.Object[]    (System.String[])
0012F184 00c2c034 System.Threading.Timer
0012F3A8 00c2bf30 System.Threading.TimerCallback
0012F3AC 00c2c008 System.Timers.ElapsedEventHandler
0012F3BC 00c2bfb0 System.Timers.Timer
0012F3C0 00c2bfb0 System.Timers.Timer
0012F3C4 00c2bfb0 System.Timers.Timer
0012F3C8 00c2bf50 System.Threading.Timer
0012F3CC 00c2bfb0 System.Timers.Timer
0012F3D0 00c2bfb0 System.Timers.Timer
0012F3D4 00c2bf50 System.Threading.Timer
0012F3D8 00c2bee4 System.Object[]    (System.String[])
0012F4C4 00c2bee4 System.Object[]    (System.String[])
0012F66C 00c2bee4 System.Object[]    (System.String[])
0012F6A0 00c2bee4 System.Object[]    (System.String[])

!gcroot -nostacks 00c2bf50

!gcroot -nostacks 00c2c034
DOMAIN(0015DC38):HANDLE(Strong):9911c0:Root:  00c2c05c(System.Threading._TimerCallback)->
  00c2bfe8(System.Threading.TimerCallback)->
  00c2bfb0(System.Timers.Timer)->
  00c2c034(System.Threading.Timer)

!gchandles
GC Handle Statistics:
Strong Handles:       22
Pinned Handles:       5
Async Pinned Handles: 0
Ref Count Handles:    0
Weak Long Handles:    0
Weak Short Handles:   0
Other Handles:        0
Statistics:
      MT    Count    TotalSize Class Name
7aa132b4        1           12 System.Diagnostics.TraceListenerCollection
79b9f720        1           12 System.Object
79ba1c50        1           28 System.SharedStatics
79ba37a8        1           36 System.Security.PermissionSet
79baa940        2           40 System.Threading._TimerCallback
79b9ff20        1           84 System.ExecutionEngineException
79b9fed4        1           84 System.StackOverflowException
79b9fe88        1           84 System.OutOfMemoryException
79b9fd44        1           84 System.Exception
7aa131b0        2           96 System.Diagnostics.DefaultTraceListener
79ba1000        1          112 System.AppDomain
79ba0104        3          144 System.Threading.Thread
79b9ff6c        2          168 System.Threading.ThreadAbortException
79b56d60        9        17128 System.Object[]
Total 27 objects

Comme John l'a souligné dans sa réponse, les deux compteurs enregistrent leur rappel (System.Threading._TimerCallback) en GCHandle table. Hans l'a souligné dans son commentaire, l' state paramètre est également maintenu en vie lorsque cela est fait.

Comme John l'a souligné, la raison en System.Timers.Timer est maintenu en vie c'est parce qu'il est référencé par le rappel (il est passé comme state paramètre à l'intérieur de la System.Threading.Timer); de même, la raison de notre System.Threading.Timer GC ed est parce qu'il n'est pas référencé par son rappel.

L'ajout d'une référence explicite à l' timer1's de rappel (par exemple, Console.WriteLine("Stayin alive (" + timer1.GetType().FullName + ")")) est suffisante pour empêcher la GC.

En utilisant le paramètre de constructeur, sur System.Threading.Timer fonctionne aussi, parce que le minuteur va alors faire référence à lui-même comme l' state paramètre. Le code suivant garde les deux minuteries vivant après la GC, depuis qu'ils sont référencés par leur rappel de l' GCHandle tableau:

class Program
{
  static void Main(string[] args)
  {
    System.Threading.Timer timer1 = null;
    timer1 = new System.Threading.Timer(_ => Console.WriteLine("Stayin alive (1)..."));
    timer1.Change(0, 400);

    var timer2 = new System.Timers.Timer
    {
      Interval = 400,
      AutoReset = true
    };
    timer2.Elapsed += (_, __) => Console.WriteLine("Stayin alive (2)...");
    timer2.Enabled = true;

    System.Threading.Thread.Sleep(2000);

    Console.WriteLine("Invoking GC.Collect...");
    GC.Collect();

    Console.ReadKey();
  }
}

31voto

John Points 3990

Vous pouvez répondre à cette question et à des questions similaires avec windbg, sos et !gcroot

 0:008> !gcroot -nostacks 0000000002354160
DOMAIN(00000000002FE6A0):HANDLE(Strong):241320:Root:00000000023541a8(System.Thre
ading._TimerCallback)->
00000000023540c8(System.Threading.TimerCallback)->
0000000002354050(System.Timers.Timer)->
0000000002354160(System.Threading.Timer)
0:008>
 

Dans les deux cas, le temporisateur natif doit empêcher la GC de l'objet de rappel (via une GCHandle). La différence est que, dans le cas de System.Timers.Timer le rappel renvoie à l'objet System.Timers.Timer (qui est implémenté en interne à l'aide de System.Threading.Timer )

9voto

Nick H Points 31

J'ai cherché sur google ce problème récemment, après avoir examiné quelques exemples d'implémentation de la Tâche.Retard et de faire quelques expériences.

Il s'avère que si oui ou non le Système.Le filetage.La minuterie est le Pgcd dépend de la façon dont vous le construire!!!

S'il est construit avec juste un rappel de l'état de l'objet sera la minuterie de lui-même et cela évitera de le GC. Cela ne semble pas être documenté nulle part et pourtant, sans elle, il est extrêmement difficile de créer du feu et oublier les minuteries.

J'ai trouvé ce à partir du code à http://www.dotnetframework.org/default.aspx/DotNET/DotNET/8@0/untmp/whidbey/REDBITS/ndp/clr/src/BCL/System/Threading/Timer@cs/1/Timer@cs

Les commentaires contenus dans ce code également indiquer pourquoi il est toujours préférable d'utiliser la fonction de rappel-seulement ctor si le rappel des références de la minuterie de l'objet retourné par la nouvelle que sinon il pourrait y avoir une course de bug.

1voto

Andy Points 4777

Dans timer1 vous êtes en lui donnant une fonction de rappel. Dans timer2 vous êtes connecter un gestionnaire d'événement; ce setups une référence à votre Programme de la classe ce qui signifie que le timer ne sera pas GCed. Puisque vous n'utilisez jamais la valeur du timer1 de nouveau, (essentiellement la même que si vous avez supprimé le var timer1 = ) le compilateur est assez intelligent pour optimiser loin de la variable. Lorsque vous appuyez sur la GC appel, rien n'est de référencement timer1 est plus tellement son " collectées.

Ajouter une Console.Writeline après votre GC appel à la sortie de l'une des propriétés du timer1 et vous remarquerez qu'il n'est pas recueilli plus.

-3voto

Jan Remunda Points 3088

Vous pouvez utiliser

 GC.KeepAlive(timer1);
 

pour empêcher la récupération de place sur cet objet.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X