114

我有一个计时器对象。我希望它每分钟运行一次。具体来说,它应该运行一个OnCallBack方法并在OnCallBack方法运行时变得不活动。一旦一个OnCallBack方法完成,它(a 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

我做错了什么?

4

5 回答 5

239

这不是 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 并且正在使用 CLR 的人,如果他们没有读过 Jeffrey Richter 的书 - CLR via C#,请尽快阅读。定时器和线程池在那里有非常详细的解释。

于 2012-10-09T09:54:35.833 回答
14

没有必要停止计时器,从这篇文章中看到很好的解决方案

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

private void CreatorLoop(object state) 
 {
   if (Monitor.TryEnter(lockObject))
   {
     try
     {
       // Work here
     }
     finally
     {
       Monitor.Exit(lockObject);
     }
   }
 }
于 2012-10-09T08:52:15.970 回答
9

System.Threading.Timer是否强制使用?

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

于 2012-10-09T09:22:32.597 回答
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 参数,因为您正在尝试自己控制周期。


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

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

于 2012-10-09T08:53:50.717 回答
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.
于 2017-07-20T15:42:56.033 回答