该类不是线程安全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();
这个程序没有运行太久。几乎立即抛出异常。它是 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
在内部使用。