似乎有System.Timers.Timer个实例通过某种机制保持了活动状态,但有System.Threading.Timer个实例没有.

具有周期性System.Threading.Timer和自动复位System.Timers.Timer的示 routine 序:

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

当我运行这个程序(.NET 4.0 Client,Release,在调试器之外)时,只有System.Threading.Timer个是GC:

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:我已经接受了下面约翰的回答,但我想解释一下.

当运行上面的示 routine 序(断点为Sleep)时,下面是相关对象和GCHandle表的状态:

!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

正如John在他的回答中指出的那样,两个定时器都在GCHandle表中注册了它们的回调(System.Threading._TimerCallback).正如Hans在他的 comments 中指出的那样,完成此操作后,state参数也保持有效.

正如John指出的,System.Timers.Timer之所以保持活动状态是因为回调引用了它(它作为state参数传递给内部System.Threading.Timer);同样,我们的System.Threading.Timer被GC’ed的原因是因为它的回调引用了not.

timer1的回调(例如Console.WriteLine("Stayin alive (" + timer1.GetType().FullName + ")"))添加显式引用足以防止GC.

System.Threading.Timer上使用单参数构造函数也可以,因为计时器随后会将自身引用为state参数.以下代码使两个计时器在GC之后保持活动状态,因为它们都被GCHandle表中的回调引用:

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

推荐答案

您可以使用winbg、sos和!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>

在这两种情况下,本地计时器都必须阻止回调对象的GC(通过GCHandle).不同之处在于,在System.Timers.Timer的情况下,回调引用System.Timers.Timer对象(它是使用System.Threading.Timer在内部实现的)

.net相关问答推荐

.NET模拟具有泛型返回类型的方法

为什么Regex.Escape支持数字符号和空格?

我的Azure应用服务从哪里获取应用设置?

将 Span 传递到函数时出现 F# 错误

当 Func 委托需要接口作为参数时,它是如何工作的?

如何正确使用await using语法?

是否可以将 SandCastle 创建的两个页面合并到一个主页中?

使用字典作为数据源绑定组合框

将客户端证书添加到 .NET Core HttpClient

为什么我得到 411 Length required 错误?

来自奥尔森时区的 .NET TimeZoneInfo

实例化具有运行时确定类型的对象

如何在 C# 7 中返回多个值?

如何将枚举值序列化为 int?

互锁且易变

beforefieldinit 标志有什么作用?

内存分配:堆栈与堆?

忽略 LINQ to XML 中的命名空间

为 webClient.DownloadFile() 设置超时

如何将两个 List 相互比较?