【问题标题】:.NET - Multiple Timers instances mean Multiple Threads?.NET - 多个计时器实例意味着多个线程?
【发布时间】:2013-08-01 18:26:27
【问题描述】:

我已经有一个运行特定工作的 System.Timers.Timer 的 Windows 服务。但是,我希望一些作品同时运行,但在不同的线程中。

我被告知要创建一个不同的 System.Timers.Timer 实例。它是否正确?这种方式可以并行运行吗?

例如:

System.Timers.Timer tmr1 = new System.Timers.Timer();
tmr1.Elapsed += new ElapsedEventHandler(DoWork1);
tmr1.Interval = 5000;

System.Timers.Timer tmr2 = new System.Timers.Timer();
tmr2.Elapsed += new ElapsedEventHandler(DoWork2);
tmr2.Interval = 5000;

tmr1tmr2 是否会在不同的线程上运行,以便 DoWork1DoWork2 可以同时运行,即同时运行?

谢谢!

【问题讨论】:

  • 不,这不是同一个问题...
  • 我已经编辑了问题...我的意思是多个 Timer 实例...
  • 我撤回了我的近距离投票。
  • same time, but in different threads 为什么?如果您使用一个计时器并在同一个计时器的经过事件中一个接一个地做这两个事情,这会不会有问题?即使 2 个计时器保证两个线程,也不能保证它们会运行 at same time - 这将取决于线程调度。

标签: multithreading c#-4.0 .net-4.0 timer


【解决方案1】:

没有错。

小心。 System.Timers.Timer 将为每个 Elapsed 事件启动一个新线程。当您的 Elapsed 事件处理程序花费太长时间时,您会遇到麻烦。您的处理程序将在另一个线程上再次调用,即使之前的调用尚未完成。这往往会产生难以诊断的错误。您可以通过将 AutoReset 属性设置为 false 来避免这种情况。还要确保在您的事件处理程序中使用 try/catch,异常会在没有诊断的情况下被吞下。

【讨论】:

  • 但是,假设我将处理 elapsed 以在前一个事件未完成之前不调用另一个事件。那么,如果我创建一个 Timer timer1 和一个 Timer timer2,每个都会在不同的线程上运行吗?这是我的疑问。
  • 见下文汉斯。也许它可以为每个 Elapsed 事件启动一个新线程,但它不需要,也没有在下面的小示例程序中。
  • 你不会去“处理过去”,定时器会。当然,如果您有两个计时器,那么您可以自动拥有两个 Elapsed 事件处理程序。它们彼此完全独立运行,它们的执行可以而且将会重叠。
  • 有点不清楚为什么我们需要一直说“是”。此时您应该编写一个小控制台模式应用程序并实际尝试它。
  • 这对我来说仍然很不清楚,所以我编辑了我的答案两次以澄清它。我没有看到这样做的问题,而且我自己的研究也没有问题。所以我请你们帮我澄清这个概念,好吗?我一直认为这就是网站的全部意义所在。
【解决方案2】:

多个计时器可能意味着多个线程。如果两个计时器滴答声同时发生(即一个正在运行,另一个触发),这两个计时器回调将在不同的线程上执行,这两个都不是主线程。

但需要注意的是,计时器本身根本不会在线程上“运行”。唯一涉及线程的时间是计时器的滴答声或经过的事件触发时。

另一方面,我强烈建议您不要使用System.Timers.Timer。计时器的已用事件会压制异常,这意味着如果异常从您的事件处理程序中逃脱,您将永远不会知道它。这是一个错误隐藏器。您应该改用System.Threading.TimerSystem.Timers.Timer 只是 System.Threading.Timer 的包装,因此您可以获得相同的计时器功能而不会隐藏错误。

请参阅Swallowing exceptions is hiding bugs 了解更多信息。

【讨论】:

    【解决方案3】:

    tmr1 和 tmr2 是否会在不同的线程上运行,以便 DoWork1 和 DoWork2 可以同时运行,即同时运行?

    一开始,是的。但是,DoWork1DoWork2 能在 5 秒内完成的保证是什么?也许您知道DoWorkX 中的代码,并假设它们将在 5 秒的时间间隔内完成,但可能会发生系统负载不足的情况,其中一项需要超过 5 秒。 这将打破您的假设,即两个DoWorkX 将在随后的滴答中同时开始。 在这种情况下,即使您随后的开始时间会同步,也存在重叠当前工作的危险执行从最后一个滴答开始仍在运行的工作执行。

    但是,如果您在 DoWorkX 中禁用/启用各自的计时器,您的开始时间将彼此不同步 - 最终它们可能会在同一个线程上一个接一个地安排。因此,如果您同意 - 随后的开始时间可能不同步 - 那么我的回答到此结束。

    如果没有,您可以尝试以下方法:

    static void Main(string[] args)
            {
                var t = new System.Timers.Timer();
                t.Interval = TimeSpan.FromSeconds(5).TotalMilliseconds;
    
                t.Elapsed += (sender, evtArgs) =>
                {
                    var timer = (System.Timers.Timer)sender;
                    timer.Enabled = false; //disable till work done
    
                    // attempt concurrent execution
                    Task work1 = Task.Factory.StartNew(() => DoWork1());
                    Task work2 = Task.Factory.StartNew(() => DoWork2());
    
    
                    Task.Factory.ContinueWhenAll(new[]{work1, work2}, 
                                _ => timer.Enabled = true); // re-enable the timer for next iteration
                };
    
                t.Enabled = true;
    
                Console.ReadLine();
            }
    

    【讨论】:

    • 感谢您的回答!如此接近终于澄清了我的疑问! :-) 但是,让我们忘记“同时开始”,我用错了词。我的重点是“并发”,也就是我要找的。回到我的问题上的代码......独立 DoWork1() 和 DoWork2() 需要不同的时间来完成,假设我启用/禁用计时器,创建 2 个计时器将保证 DoWork1() 和 DoWork2() 总是同时运行?再次感谢!
    • 我喜欢你的代码,但你的第一段是错误的,因为无论执行需要多长时间,计时器都会继续运行。从我引用 MSDN 的帖子中:如果 Elapsed 事件的处理持续时间超过 Interval,则该事件可能会在另一个 ThreadPool 线程上再次引发。在这种情况下,事件处理程序应该是可重入的。
    • @Kevin:啊,我认为你是对的。非常感谢您指出。进行了编辑。
    • @AndreMiranda:concurrent 我的理解是两种方法同时运行(至少大约)。如果您在其处理程序中启用/禁用单个计时器,则后续滴答声将不同步并且不会在 same 时间运行。然而,重要的是计时器与线程没有关联。计时器的每个滴答都安排在线程池中的任何随机线程上。因此,如果它们的开始时间不同步,它们可能会被安排在同一个线程上。
    • @YK1 我如何使用 try catch 并最终使用这个?我很好奇我是否可以在 finally 博客中发现任何错误并重置计时器。
    【解决方案4】:

    有点。首先,查看 System.Timers.Timer 的 MSDN 页面:http://msdn.microsoft.com/en-us/library/system.timers.timer.aspx

    下面引用你需要关注的部分:

    如果 SynchronizingObject 属性为 null,则 Elapsed 事件为 在 ThreadPool 线程上引发。如果处理 Elapsed 事件 持续时间长于间隔,该事件可能会再次引发另一个 线程池线程。在这种情况下,事件处理程序应该是 可重入。

    基本上,这意味着Timer 的动作在哪里运行并不是每个Timer 都有自己的线程,而是默认情况下,它使用系统ThreadPool 来运行动作。

    如果您希望事情同时运行(同时启动)但同时运行,您可以只是将多个事件放在经过的事件上。例如,我在 VS2012 中尝试过:

        static void testMethod(string[] args)
        {
            System.Timers.Timer mytimer = new System.Timers.Timer();
            mytimer.AutoReset = false;
            mytimer.Interval = 3000;
    
            mytimer.Elapsed += (x, y) => {
                Console.WriteLine("First lambda.  Sleeping 3 seconds");
                System.Threading.Thread.Sleep(3000);
                Console.WriteLine("After sleep");
            };
            mytimer.Elapsed += (x, y) => { Console.WriteLine("second lambda"); };
            mytimer.Start();
            Console.WriteLine("Press any key to go to end of method");
            Console.ReadKey();
        }
    

    输出是这样的:

    按任意键转到方法的结尾

    第一个 lambda。

    睡了 3 秒

    睡后

    第二个 lambda

    所以它连续执行它们,而不是同时执行。因此,如果您希望在每次计时器执行时“发生一堆事情”,则必须在您的 Elapsed 处理程序中启动一堆任务(或使用操作排队 ThreadPool)。它可能多线程,也可能不,但在我的简单示例中,它没有。

    自己试试我的代码,很容易说明发生了什么。

    【讨论】:

    • 这是非常好的信息,这个答案没有任何问题。但是,由于 OP 提到了一个 Windows 服务,因此可能可以安全地假设 SynchronizingObject 将是 null
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-14
    • 1970-01-01
    相关资源
    最近更新 更多