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();
这个程序没有运行太长时间。几乎立即就会抛出异常。可能是
NullReferenceException
或者
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
可以被多个线程并行调用而不会抛出任何异常。其
源代码表明它是线程安全的,因为它在内部使用了
lock
。