C# System.Timers.Timer 与 System.Threading.Timer 的线程安全

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19577296/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 15:20:30  来源:igfitidea点击:

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

c#.netmultithreadingtimerthread-safety

提问by Pragmateek

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

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

Since then this has been repeated on blogs, in Richter's book "CLR via C#", on SO, but this is never justified.

从那以后,这在博客上重复出现,在 Richter 的书“CLR via C#”中,关于 SO,但这从来没有道理。

Moreover the MSDN documentationassures "This type is thread safe."

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

1) Who tells the truth?

1)谁说的是真话?

2) If this is the original article what makes System.Threading.Timernot thread-safe and how its wrapper System.Timers.Timerachieves more thread-safety?

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

Thanks

谢谢

采纳答案by Hans Passant

No, that's not the way it works. The .NET asynchronous Timer classes are perfectly thread-safe. The problem with thread-safety is that it is not a transitive property, it doesn't make the othercode that's executed thread-safe as well. The code that you wrote, not a .NET Framework programmer.

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

It is the same kind of problem with the very common assumption that Windows UI code is fundamentally thread-unsafe. It is not, the code inside Windows is perfectly thread-safe. The problem is all the code that runs that is notpart of Windows and not written by a Microsoft programmer. There's always a lotof that code, triggered by a SendMessage() call. Which runs custom code that a programmer wrote. Or code he didn't write, like a hook installed by some utility. Code that assumes that the program doesn't make it difficult and just executes message handlers on one thread. He usually does, not doing that buys him a lotof trouble.

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

Same problem with the System.Timers.Timer.Elapsed event and the System.Threading.Timer callback. Programmers make lotsof mistakes writing that code. It runs complete asynchronously on an arbitrary threadpool thread, touching any shared variable really does require locking to protect state. Very easy to overlook. And worse, much worse, very easy to get yourself into a pile of trouble when the code runs again, before the previous invocation stopped running. Triggered when the timer interval is too low or the machine is too heavily loaded. Now there are twothreads running the same code, that rarely comes to a good end.

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

Threading is hard, news at eleven.

线程很难,十一点的消息。

回答by Theodor Zoulias

The System.Timers.Timerclass is not thread safe. Here is how it can be proved. A single Timerinstance is created, and its property Enabledis 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...

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

This program is not running for too long. An exception is thrown almost immediately. It is either a NullReferenceExceptionor an ObjectDisposedException:

该程序运行时间不长。几乎立即抛出异常。它是一个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 . . .

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

在研究了类的源代码后,发生这种情况的原因很明显。当类的内部字段改变时,没有同步。因此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();

Regarding the System.Threading.Timerclass, it has no properties, and its single method Changecan be called by multiple threads in parallel without any exceptions thrown. Its source codeindicates that it's thread safe, since a lockis used internally.

关于System.Threading.Timer类,它没有任何属性,它的单个方法Change可以被多个线程并行调用而不会抛出任何异常。它的源代码表明它是线程安全的,因为 alock在内部使用。