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 对象被锁定后立即返回,开销并不大,特别是如果你有这么大的间隔。 这并不能保证在最后一次执行后不少于是否必须使用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()