C#计时器是否在单独的线程上运行?


98

System.Timers.Timer是否在与创建它的线程不同的线程上运行?

可以说我有一个带有计时器的类,该计时器每5秒触发一次。当计时器触发时,在经过的方法中,某些对象被修改。可以说,修改该对象需要很长时间,例如10秒。在这种情况下,是否可能会遇到线程冲突?


这可能会导致问题。请注意,通常,线程池线程不是为长时间运行的进程而设计的。
Greg D

我正在使用Windows服务进行测试。对我有用的是禁用计时器作为OnTimer事件中的第一条指令,执行我的任务,然后在最后启用计时器。这已经在生产环境中可靠地工作了一段时间。
史蒂夫

Answers:


60

对于System.Timers.Timer

请参阅下面的Brian Gideon的答案

对于System.Threading.Timer

MSDN关于计时器的文档指出:

System.Threading.Timer类在ThreadPool线程上进行回调,并且完全不使用事件模型。

因此,计时器确实在另一个线程上运行了。


21
是的,但这是完全不同的类。OP询问有关System.Timers.Timer类的信息。
布赖恩·吉迪恩

1
哦,对了。msdn.microsoft.com/zh-cn/library/system.timers.timer.aspx说:“在ThreadPool线程上引发了Elapsed事件。” 我想从那里得出同样的结论。
Joren

6
是的,但这并不是那么简单。看我的答案。
布赖恩·吉迪恩

192

这取决于。将System.Timers.Timer有两种操作模式。

如果SynchronizingObject将设置为ISynchronizeInvoke实例,则Elapsed事件将在托管同步对象的线程上执行。通常这些ISynchronizeInvoke实例是没有比普通的老式其他ControlForm情况下,我们都熟悉。因此,在那种情况下,Elapsed事件是在UI线程上调用的,其行为类似于System.Windows.Forms.Timer。否则,它实际上取决于所使用的特定ISynchronizeInvoke实例。

如果SynchronizingObject为null,则在线程Elapsed上调用该事件ThreadPool,其行为类似于System.Threading.Timer。实际上,它实际上在System.Threading.Timer后台使用了一个幕后,并在收到计时器回调(如果需要)之后进行编组操作。


4
如果您希望计时器回调在新线程上执行,则应使用System.Threading.Timeror还是System.Timers.Timer
2012年

1
@ cj7:谁都可以做到。
Brian Gideon

如果我有一个复杂类型(人)的列表,并且想在每个人中都有时间?我需要在同一线程(所有人)上运行它,因为如果它调用第一人称方法,那么第二人必须等待,直到第一个人结束已发生的事件。我可以那样做吗?
Leandro De Mello Fagundes 2014年

4
每个人System.Timers.Timer都有两种操作模式。它可以在随机分配的线程池线程上运行,也可以在托管ISynchronizeInvoke实例的任何线程上运行。我不知道该如何澄清。System.Threading.Timer与原始问题关系不大(如果有的话)。
Brian Gideon

@LeandroDeMelloFagundes不能使用lock吗?
Ozkan

24

除非先前的“经过”仍在运行,否则每个经过的事件都将在同一线程中触发。

因此它为您处理了碰撞

尝试将其放在控制台中

static void Main(string[] args)
{
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
    var timer = new Timer(1000);
    timer.Elapsed += timer_Elapsed;
    timer.Start();
    Console.ReadLine();
}

static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    Thread.Sleep(2000);
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
}

你会得到这样的东西

10
6
12
6
12

其中10是调用线程,而6和12正在从bg过去的事件中触发。如果删除Thread.Sleep(2000); 你会得到这样的东西

10
6
6
6
6

由于没有碰撞。

但这仍然给您带来问题。如果u每5秒触发一次事件,并且需要10秒钟进行编辑,则需要进行一些锁定才能跳过某些编辑。


8
timer.Stop()在Elapsed事件方法的开头添加a ,然后在Elapsed事件方法timer.Start()的末尾添加a 可以防止Elapsed事件发生冲突。
地铁蓝精灵

4
您不需要放置timer.Stop(),只需要定义timer.AutoReset = false;即可。然后在处理事件后创建timer.Start()。我认为这是避免冲突的更好方法。
若昂·安图内斯

17

对于System.Timers.Timer,如果未设置SynchronizingObject,则在单独的线程上。

    static System.Timers.Timer DummyTimer = null;

    static void Main(string[] args)
    {
        try
        {

            Console.WriteLine("Main Thread Id: " + System.Threading.Thread.CurrentThread.ManagedThreadId);

            DummyTimer = new System.Timers.Timer(1000 * 5); // 5 sec interval
            DummyTimer.Enabled = true;
            DummyTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnDummyTimerFired);
            DummyTimer.AutoReset = true;

            DummyTimer.Start();

            Console.WriteLine("Hit any key to exit");
            Console.ReadLine();
        }
        catch (Exception Ex)
        {
            Console.WriteLine(Ex.Message);
        }

        return;
    }

    static void OnDummyTimerFired(object Sender, System.Timers.ElapsedEventArgs e)
    {
        Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
        return;
    }

输出,您会看到DummyTimer是否每隔5秒触发一次:

Main Thread Id: 9
   12
   12
   12
   12
   12
   ... 

因此,可以看出,OnDummyTimerFired是在Workers线程上执行的。

不,进一步的复杂化-如果您将间隔缩短为10毫秒,

Main Thread Id: 9
   11
   13
   12
   22
   17
   ... 

这是因为,如果在触发下一个刻度时未完成OnDummyTimerFired的先前执行,则.NET将创建一个新线程来执行此工作。

更复杂的是,“ System.Timers.Timer类提供了一种解决此难题的简便方法,它公开了一个公共的SynchronizingObject属性。将该属性设置为Windows Form实例(或Windows Form上的控件)将确保您的Elapsed事件处理程序中的代码将在实例化SynchronizingObject的同一线程上运行。”

http://msdn.microsoft.com/zh-CN/magazine/cc164015.aspx#S2


这是一个很好的例子。直到现在我还不知道,我需要在这里和那里设置一些锁。完善。
Olaru Mircea 2015年

如果我希望计时器在控制台应用程序的主线程中触发Elapsed事件,该怎么办?
jacktric

13

如果经过的事件花费的时间比间隔时间长,它将创建另一个线程来引发经过的事件。但是有一个解决方法

static void timer_Elapsed(object sender, ElapsedEventArgs e)    
{     
   try
   {
      timer.Stop(); 
      Thread.Sleep(2000);        
      Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);    
   }
   finally
   {
     timer.Start();
   }
}

+1简单明了的答案,但这并不总能奏效。而是应该使用计时器的锁或SynchronizingObject属性。
RollerCosta
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.