C#中的System.Threading.Timer似乎不起作用。每三秒钟运行一次


112

我有一个计时器对象。我希望它每分钟运行一次。具体来说,它应运行一个OnCallBack方法,并在OnCallBack方法运行时变为非活动状态。一旦一个OnCallBack方法完成,它(一个OnCallBack)重新启动的定时器。

这是我现在拥有的:

private static Timer timer;

private static void Main()
{
    timer = new Timer(_ => OnCallBack(), null, 0, 1000 * 10); //every 10 seconds
    Console.ReadLine();
}

private static void OnCallBack()
{
    timer.Change(Timeout.Infinite, Timeout.Infinite); //stops the timer
    Thread.Sleep(3000); //doing some long operation
    timer.Change(0, 1000 * 10);  //restarts the timer
}

但是,它似乎不起作用。它每3秒运行一次非常快。即使加一个周期(1000 * 10)。似乎对它视而不见1000 * 10

我做错什么了?


12
来自Timer.Change:“如果DueTime为零(0),则立即调用回调方法。”。看起来对我来说是零。
Damien_The_Unbeliever 2012年

2
是的,那又如何呢?还有一个时期。
艾伦·科罗马诺

10
那如果还有一个时期怎么办?引用的句子对期间值没有要求。它只是说“如果该值为零,我将立即调用回调”。
Damien_The_Unbeliever

3
有趣的是,如果将DueTime和period都设置为0,计时器将每秒运行一次并立即启动。
开尔文

Answers:


230

这不是System.Threading.Timer的正确用法。实例化Timer时,几乎应该始终执行以下操作:

_timer = new Timer( Callback, null, TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite );

这将指示计时器仅在间隔过去后计时一次。然后在回调功能中,您只需在工作完成后(而不是之前)更改计时器。例:

private void Callback( Object state )
{
    // Long running operation
   _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite );
}

因此,因为没有并发性,所以不需要锁定机制。在下一个间隔+长时间运行的时间过去之后,计时器将触发下一个回调。

如果您需要精确地在N毫秒内运行计时器,则建议您使用Stopwatch测量长时间运行的时间,然后适当地调用Change方法:

private void Callback( Object state )
{
   Stopwatch watch = new Stopwatch();

   watch.Start();
   // Long running operation

   _timer.Change( Math.Max( 0, TIME_INTERVAL_IN_MILLISECONDS - watch.ElapsedMilliseconds ), Timeout.Infinite );
}

强烈鼓励任何使用.NET并且正在使用尚未阅读Jeffrey Richter的书- 通过C#进行CLR的CLR的人尽快阅读。计时器和线程池在此进行了详细说明。


6
我不同意private void Callback( Object state ) { // Long running operation _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite ); }Callback可能在操作完成之前再次调用。
艾伦·科罗马诺

2
我的意思是那Long running operation可能要花更多的时间TIME_INTERVAL_IN_MILLISECONDS。那会发生什么呢?
艾伦·科罗马诺

31
回调将不会再次被调用,这就是重点。这就是为什么我们将Timeout.Infinite作为第二个参数传递的原因。这基本上意味着不要再为计时器打勾。完成操作后,请重新安排时间以打勾。
伊万·兹拉塔诺夫

线程到这里的新手- ThreadPool如果您传入了计时器,您是否认为可以用来实现?我正在考虑一个场景,其中会生成一个新线程以一定的时间间隔执行一项工作,然后在完成时降级到线程池中。
jedd.ahyoung 2014年

2
System.Threading.Timer是一个线程池计时器,它正在线程池而不是专用线程上执行其回调。计时器完成回调例程后,执行回调的线程将返回池中。
Ivan Zlatanov 2014年

14

不必停止计时器,请参阅这篇文章的解决方案

“您可以让计时器继续触发回调方法,但将不可重入的代码包装在Monitor.TryEnter / Exit中。在这种情况下,无需停止/重新启动计时器;重叠的调用将不会获取锁定并立即返回。”

private void CreatorLoop(object state) 
 {
   if (Monitor.TryEnter(lockObject))
   {
     try
     {
       // Work here
     }
     finally
     {
       Monitor.Exit(lockObject);
     }
   }
 }

这不是我的情况。我需要确切地停止计时器。
艾伦·科罗马诺

您是否试图阻止多次输入回调?如果没有,您想达到什么目的?
伊万·列昂

1.防止多次进入回调。2.防止执行太多时间。
艾伦·科罗马诺

这正是它的作用。#2开销不大,只要它在if语句后(如果对象已锁定)后立即返回,尤其是在间隔很大的情况下。
伊万·列昂

1
这不能保证在最后一次执行之后调用该代码的时间不少于<interval>(在上一个滴答释放了锁定之后,可能会在微秒内触发计时器的新滴答)。这取决于是否是严格的要求(从问题描述中还不能完全清楚)。
Marco Mp 2012年

9

使用System.Threading.Timer强制性吗?

如果不是,则System.Timers.Timer具有handy Start()Stop()methods(以及AutoReset可以设置为false 的属性,这样Stop()就不需要了,您只需调用Start()在执行后)。


3
是的,但这可能是一个真正的要求,或者恰好因为它是最常用的计时器而选择了计时器。遗憾的是,.NET有大量的计时器对象,重叠了90%,但仍然(有时是微妙的)不同。当然,如果有要求,此解决方案根本不适用。
Marco Mp 2012年

2
根据文档:Systems.Timer类仅在.NET Framework中可用。它不包括在.NET标准库中,并且在其他平台(例如.NET Core或通用Windows平台)
NotAgain说恢复莫妮卡的时间

3

我会做:

private static Timer timer;
 private static void Main()
 {
   timer = new Timer(_ => OnCallBack(), null, 1000 * 10,Timeout.Infinite); //in 10 seconds
   Console.ReadLine();
 }

  private static void OnCallBack()
  {
    timer.Dispose();
    Thread.Sleep(3000); //doing some long operation
    timer = new Timer(_ => OnCallBack(), null, 1000 * 10,Timeout.Infinite); //in 10 seconds
  }

并忽略period参数,因为您试图自己控制周期。


您的原始代码是尽可能快地运行,因为你把指定0dueTime参数。来自Timer.Change

如果DueTime为零(0),则立即调用回调方法。


2
是否需要配置计时器?你为什么不使用Change()方法?
艾伦·科罗马诺

21
每次都设置计时器绝对是不必要的,也是错误的。
伊万·兹拉塔诺夫

0
 var span = TimeSpan.FromMinutes(2);
 var t = Task.Factory.StartNew(async delegate / () =>
   {
        this.SomeAsync();
        await Task.Delay(span, source.Token);
  }, source.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

source.Cancel(true/or not);

// or use ThreadPool(whit defaul options thread) like this
Task.Start(()=>{...}), source.Token)

如果你喜欢在里面使用一些循环线程...

public async void RunForestRun(CancellationToken token)
{
  var t = await Task.Factory.StartNew(async delegate
   {
       while (true)
       {
           await Task.Delay(TimeSpan.FromSeconds(1), token)
                 .ContinueWith(task => { Console.WriteLine("End delay"); });
           this.PrintConsole(1);
        }
    }, token) // drop thread options to default values;
}

// And somewhere there
source.Cancel();
//or
token.ThrowIfCancellationRequested(); // try/ catch block requred.
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.