24

在这篇文章中:http: //msdn.microsoft.com/en-us/magazine/cc164015.aspx作者指出System.Threading.Timer 不是线程安全的

从那时起,这一直在博客上重复出现,在 Richter 的书“CLR via C#”中,关于 SO,但这从来没有被证明是合理的。

此外,MSDN 文档保证“这种类型是线程安全的”。

1) 谁说真话?

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

谢谢

4

2 回答 2

47

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

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

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

线程很难,十一点的新闻。

于 2013-10-24T22:33:53.937 回答
3

该类不是线程安全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();

这个程序没有运行太久。几乎立即抛出异常。它是 aNullReferenceException或 an ObjectDisposedException

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 . . .

在研究了该类的源代码之后,发生这种情况的原因非常明显。当类的内部字段改变时没有同步。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可以被多个线程并行调用,不会抛出任何异常。它的源代码表明它是线程安全的,因为 alock在内部使用。

于 2020-03-29T10:31:05.167 回答