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

时间:2013-10-24 21:45:54

标签: c# .net multithreading timer thread-safety

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

从那以后,在Rich的书“CLR via C#”中,在博客上重复了这一点,但这绝不合理。

此外MSDN documentation确保“此类型是线程安全的。”

1)谁说实话?

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

由于

2 个答案:

答案 0 :(得分:42)

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

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

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

线程很难,11点新闻。

答案 1 :(得分:1)

System.Timers.Timer不是线程安全的。这是可以证明的方式。创建了一个Timer实例,并且其属性Enabled被两个并行运行的不同线程无限切换。如果该类是线程安全的,则其内部状态不会被破坏。让我们看看...

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

该程序运行时间过长。几乎立即引发异常。它是NullReferenceExceptionObjectDisposedException

System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Timers.Timer.UpdateTimer()
   at System.Timers.Timer.set_Enabled(Boolean value)
   at Program.<>c__DisplayClass1_0.<Main>b__1()
   at System.Threading.Tasks.Task`1.InnerInvoke()
   at System.Threading.Tasks.Task.<>c.<.cctor>b__274_0(Object obj)
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location where exception was thrown ---
   at Program.Main(String[] args)
Press any key to continue . . .

System.ObjectDisposedException: Cannot access a disposed object.
   at System.Threading.TimerQueueTimer.Change(UInt32 dueTime, UInt32 period)
   at System.Threading.Timer.Change(Int32 dueTime, Int32 period)
   at System.Timers.Timer.UpdateTimer()
   at System.Timers.Timer.set_Enabled(Boolean value)
   at Program.<>c__DisplayClass1_0.<Main>b__1()
   at System.Threading.Tasks.Task`1.InnerInvoke()
   at System.Threading.Tasks.Task.<>c.<.cctor>b__274_0(Object obj)
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location where exception was thrown ---
   at Program.Main(String[] args)
Press any key to continue . . .

研究了课程的source code后,出现这种情况的原因非常明显。更改类的内部字段时,没有同步。因此,当实例并行被多个线程变异时,必须手动同步对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();

关于System.Threading.Timer类,它没有属性,并且多个线程可以并行调用其单个方法Change,而不会引发任何异常。其source code表示它是线程安全的,因为内部使用lock