C# 中的 System.Threading.Timer 似乎无法正常工作。它每 3 秒运行一次非常快

Posted

技术标签:

【中文标题】C# 中的 System.Threading.Timer 似乎无法正常工作。它每 3 秒运行一次非常快【英文标题】:System.Threading.Timer in C# it seems to be not working. It runs very fast every 3 second 【发布时间】:2012-09-29 13:19:23 【问题描述】:

我有一个计时器对象。我希望它每分钟运行一次。具体来说,它应该运行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视而不见@

我做错了什么?

【问题讨论】:

来自Timer.Change:“如果dueTime为零(0),则立即调用回调方法。”。对我来说似乎是零。 是的,但那又怎样?也有一个时期。 如果也有月经怎么办?引用的句子没有对期间价值提出任何要求。它只是说“如果这个值为零,我将立即调用回调”。 有趣的是,如果你将dueTime和period都设置为0,计时器将每秒运行一次并立即启动。 【参考方案1】:

这不是 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#,请尽快阅读。定时器和线程池在那里有非常详细的解释。

【讨论】:

我不同意private void Callback( Object state ) // Long running operation _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite ); Callback 可能会在操作完成之前再次调用。 我的意思是Long running operation 可能比TIME_INTERVAL_IN_MILLISECONDS 花费更多的时间。那会发生什么? 回调不会再被调用,这就是重点。这就是我们将 Timeout.Infinite 作为第二个参数传递的原因。这基本上意味着不要再为计时器打勾。然后在我们完成操作后重新安排打勾。 这里是线程的新手——如果你传入计时器,你认为这可能与ThreadPool 相关吗?我正在考虑这样一种情况,即在某个时间间隔产生一个新线程来执行一项工作 - 然后在完成时降级到线程池。 System.Threading.Timer 是一个线程池计时器,它在线程池上执行它的回调,而不是专用线程。定时器完成回调例程后,执行回调的线程回到池中。【参考方案2】:

定时器不需要停止,see nice solution from this post:

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

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

【讨论】:

这不适合我的情况。我需要准确地停止计时器。 您是否试图阻止多次输入回调?如果没有,你想达到什么目的? 1.防止多次进入回调。 2.防止执行次数过多。 这正是它的作用。 #2 只要在 if 语句 if 对象被锁定后立即返回,开销并不大,特别是如果你有这么大的间隔。 这并不能保证在最后一次执行后不少于 调用代码(可以在前一个滴答释放锁后一微秒触发一个新的计时器滴答)。这取决于这是否是一个严格的要求(从问题的描述中并不完全清楚)。【参考方案3】:

是否必须使用System.Threading.Timer

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

【讨论】:

是的,但这可能是一个真正的要求,或者只是碰巧选择了计时器,因为它是最常用的。遗憾的是,.NET 有大量的计时器对象,重叠了 90%,但仍然(有时微妙地)不同。当然,如果是需求的话,这个方案根本不适用。 根据documentation:Systems.Timer 类仅在 .NET Framework 中可用。它不包含在 .NET 标准库中,并且在其他平台上不可用,例如 .NET Core 或通用 Windows 平台。在这些平台上,以及跨所有 .NET 平台的可移植性,您应该改用 System.Threading.Timer 类。【参考方案4】:

我会这样做:

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 参数,因为您正在尝试自己控制周期。


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

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

【讨论】:

是否需要配置定时器?为什么不用Change() 方法? 每次都设置计时器是绝对没有必要和错误的。 @IvanZlatanov 您能否就此提供更多见解。当我们调用 dispose 时,对象将从内存中清除,这很好,对吧?我所看到的只是一些可能影响解决方案的额外过程。只是大声思考...... @SudhakarChavali 您应该始终在使用完对象后将其丢弃。在这个例子中,因为定时器有 Change 方法可以启用/禁用实例,所以不需要释放它并创建一个新的。【参考方案5】:
 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.

【讨论】:

以上是关于C# 中的 System.Threading.Timer 似乎无法正常工作。它每 3 秒运行一次非常快的主要内容,如果未能解决你的问题,请参考以下文章

c# 中的 readInt16() 与 java 中的 readShort()

C#中的语句

使用托管 C++ 项目中的 C# 类

C#:相当于 C# 中的 HTML5 Canvas

C# 转换中的 HttpURLConnection REST API

C#的基础—C#中的方法