Thread-safety of System.Timers.Timer vs System.Threading.Timer

后端 未结 2 1720
遥遥无期
遥遥无期 2020-12-14 08:26

In this article: http://msdn.microsoft.com/en-us/magazine/cc164015.aspx the author states that System.Threading.Timer is not thread-safe.

Since then

2条回答
  •  -上瘾入骨i
    2020-12-14 09:05

    The System.Timers.Timer class is not thread safe. Here is how it can be proved. A single Timer instance is created, and its property Enabled is toggled endlessly by two different threads that are running in parallel. If the class is thread safe, its internal state will not be corrupted. Lets see...

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

    This program is not running for too long. An exception is thrown almost immediately. It is either a NullReferenceException or 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.
    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.
    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 . . .

    The reason this happens is quite evident, after studying the source code of the class. There is no synchronization when the internal fields of the class are changed. So synchronizing manually the access to a Timer instance is mandatory, when this instance is mutated by multiple threads in parallel. For example the program below runs forever without throwing any exception.

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

    Regarding the System.Threading.Timer class, it has no properties, and its single method Change can be called by multiple threads in parallel without any exceptions thrown. Its source code indicates that it's thread safe, since a lock is used internally.

提交回复
热议问题