System.Timers.Timer与System.Threading.Timer的线程安全性

Pra*_*eek 22 .net c# multithreading timer thread-safety

在本文中:http://msdn.microsoft.com/en-us/magazine/cc164015.aspx作者声明System.Threading.Timer不是线程安全的.

从那时起,在Rich的书"CLR via C#"中,在博客上重复了这一点,但是这一点从来都不合理.

此外,MSDN文档确保"此类型是线程安全的".

1)谁说实话?

2)如果这是原始文章是什么使System.Threading.Timer不是线程安全的,它的包装器System.Timers.Timer如何实现更多线程安全?

谢谢

Han*_*ant 46

不,这不是它的工作方式..NET异步Timer类是完全线程安全的.线程安全的问题在于它不是传递属性,它也不会使其他代码也执行线程安全.您编写的代码,而不是.NET Framework程序员.

与Windows UI代码基本上是线程不安全的非常普遍的假设是同一类问题.事实并非如此,Windows中的代码完全是线程安全的.问题是运行的所有代码都不是Windows的一部分,而不是由Microsoft程序员编写的.由SendMessage()调用触发的代码总是很多.它运行程序员编写的自定义代码.或者他没有编写的代码,就像某个实用程序安装的钩子一样.假定程序不会使其变得困难并且只在一个线程上执行消息处理程序的代码.他经常做,不这样做会给他带来很多麻烦.

与System.Timers.Timer.Elapsed事件和System.Threading.Timer回调相同的问题.程序员在编写代码时犯了很多错误.它在任意线程池线程上异步运行,触摸任何共享变量确实需要锁定以保护状态.很容易被忽视.更糟糕的是,更糟糕的是,在上一次调用停止运行之前,当代码再次运行时,很容易陷入一堆麻烦.定时器间隔过低或机器负载过重时触发.现在有两个线程运行相同的代码,很少有好的结果.

线程很难,新闻十一点.

  • @mishrsud:之所以没有接受这个答案的原因不是它的质量确实很好但是仍然存在合理的怀疑,因为其他来源对这个主题有相反的观点.但让我们考虑汉斯作为权威来源(恕我直言,这是一个合理的假设),所以我会接受它.:) (4认同)
  • 感谢Hans提醒我们这个基本原则.我知道这个问题(以艰难的方式学习;)).但有些人(而不仅仅是任何人)表示这些计时器之间的根本区别在于线程安全.正如Henk所讨论的那样,你说两者都应该被认为是线程安全的,或者两者都应该被认为是不是线程安全的.也许解释很简单:原始来源是错误的(例如,因为基于早期的alpha实现)而其他人只是重复了这个错误的陈述.但我想确定一下. (2认同)
  • 正如警告:System.Timers.Timer 中的 Enabled 属性不是线程保存的。只要看看它的来源就可以了。(就像几乎所有 .Net 类一样,doku 告诉我们:这种类型的任何公共静态成员都是线程安全的。任何实例成员都不能保证是线程安全的。) (2认同)
  • _“.NET 异步 Timer 类是完全线程安全的”_ -- 不,只有 `System.Threading.Timer` 类被记录为线程安全的。`System.Timers.Timer` 类被专门记录为_不_保证线程安全,否则假设是不明智的。 (2认同)

The*_*ias 11

该类不是线程安全System.Timers.Timer的。这是如何证明的。创建一个实例,其属性由两个并行运行的不同线程无休止地切换。如果类是线程安全的,则其内部状态不会被破坏。让我们来看看...TimerEnabled

var timer = new System.Timers.Timer();
var tasks = Enumerable.Range(1, 2).Select(x => Task.Run(() =>
{
    while (true)
    {
        timer.Enabled = true;
        timer.Enabled = false;
    }
})).ToArray();
Task.WhenAny(tasks).Unwrap().GetAwaiter().GetResult();
Run Code Online (Sandbox Code Playgroud)

该程序运行时间不长。几乎立即抛出异常。它可以是 aNullReferenceException或 an ObjectDisposedException

var timer = new System.Timers.Timer();
var tasks = Enumerable.Range(1, 2).Select(x => Task.Run(() =>
{
    while (true)
    {
        timer.Enabled = true;
        timer.Enabled = false;
    }
})).ToArray();
Task.WhenAny(tasks).Unwrap().GetAwaiter().GetResult();
Run Code Online (Sandbox Code Playgroud)

在研究了该类的源代码之后,发生这种情况的原因非常明显。当类的内部字段更改时,不存在同步。Timer因此,当多个线程并行改变实例时,必须手动同步对实例的访问。例如,下面的程序将永远运行,不会抛出任何异常。

var locker = new object();
var timer = new System.Timers.Timer();
var tasks = Enumerable.Range(1, 2).Select(x => Task.Run(() =>
{
    while (true)
    {
        lock (locker) timer.Enabled = true;
        lock (locker) timer.Enabled = false;
    }
})).ToArray();
Task.WhenAny(tasks).Unwrap().GetAwaiter().GetResult();
Run Code Online (Sandbox Code Playgroud)

对于System.Threading.Timer类来说,它没有属性,并且它的单个方法Change可以被多个线程并行调用,而不会抛出任何异常。它的源代码表明它是线程安全的,因为 alock是在内部使用的。

  • 这是正确的答案,也与两个类的官方文档一致。 (2认同)