如何最简单地设置计时器,在每30秒运行该任务而不重叠执行?(我假设
System.Threading.Timer
是正确的计时器,但可能是错的)。System.Threading.Timer
是正确的计时器,但可能是错的)。你可以使用一个定时器来完成这个任务,但需要在数据库扫描和更新时进行某种形式的锁定。一个简单的 lock
同步可能足以防止多次运行。
话虽如此,最好是在操作完成后再启动定时器,并仅使用一次,然后停止它。在下一次操作之后重新启动它。这样将会给你30秒(或N秒)的事件间隔,没有重叠的机会,也不需要锁定。
例如:
System.Threading.Timer timer = null;
timer = new System.Threading.Timer((g) =>
{
Console.WriteLine(1); //do whatever
timer.Change(5000, Timeout.Infinite);
}, null, 0, Timeout.Infinite);
立即工作...完成...等待5秒钟...立即工作...完成...等待5秒钟...
我会在你的经过时间的代码中使用 Monitor.TryEnter:
if (Monitor.TryEnter(lockobj))
{
try
{
// we got the lock, do your work
}
finally
{
Monitor.Exit(lockobj);
}
}
else
{
// another elapsed has the lock
}
我更喜欢使用System.Threading.Timer
来处理这种事情,因为我不需要经过事件处理机制:
Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, 30000);
object updateLock = new object();
void UpdateCallback(object state)
{
if (Monitor.TryEnter(updateLock))
{
try
{
// do stuff here
}
finally
{
Monitor.Exit(updateLock);
}
}
else
{
// previous timer tick took too long.
// so do nothing this time through.
}
}
通过将计时器设置为单次触发并在每次更新后重新启动它,可以消除使用锁的需要:
// Initialize timer as a one-shot
Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, Timeout.Infinite);
void UpdateCallback(object state)
{
// do stuff here
// re-enable the timer
UpdateTimer.Change(30000, Timeout.Infinite);
}
PeriodicTimer
。这是一个轻量级的异步启用计时器,当严格禁止重叠执行时成为完美工具。您可以通过编写带有循环的异步方法并调用它来启动循环来使用此计时器:private Task _operation;
private CancellationTokenSource _operationCancellation = new();
//...
_operation = StartTimer();
//...
private async Task StartTimer()
{
PeriodicTimer timer = new(TimeSpan.FromSeconds(30));
while (true)
{
await timer.WaitForNextTickAsync(_operationCancellation.Token);
try
{
DoSomething();
}
catch (Exception ex)
{
_logger.LogError(ex);
}
}
}
除了使用 CancellationTokenSource
,您还可以通过 释放 PeriodicTimer
来停止循环。在这种情况下,await timer.WaitForNextTickAsync()
将返回 false
。
可能会以小于 30 秒的更短时间间隔连续调用 DoSomething
,但不可能以重叠方式调用它,除非您意外启动了两个异步循环。
该计时器不支持禁用和重新启用。如果需要此功能,您可以查看第三方 Nito.AsyncEx.PauseTokenSource
组件。
如果您的目标是 .NET 6 之前的 .NET 版本,则可以查看此问题以获取替代方法:定期运行异步方法并指定时间间隔。
不要使用锁定(这可能会导致所有的定时扫描等待并最终堆积)。您可以在一个线程中启动扫描/更新,然后只需检查线程是否仍然存在。
Thread updateDBThread = new Thread(MyUpdateMethod);
...
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
if(!updateDBThread.IsAlive)
updateDBThread.Start();
}
// Somewhere else in the code
using System;
using System.Threading;
// In the class or whever appropriate
static AutoResetEvent autoEvent = new AutoResetEvent(false);
void MyWorkerThread()
{
while(1)
{
// Wait for work method to signal.
if(autoEvent.WaitOne(30000, false))
{
// Signalled time to quit
return;
}
else
{
// grab a lock
// do the work
// Whatever...
}
}
}
using System;
using System.Diagnostics;
using System.Threading;
// In the class or whever appropriate
static AutoResetEvent autoEvent = new AutoResetEvent(false);
void MyWorkerThread()
{
Stopwatch stopWatch = new Stopwatch();
TimeSpan Second30 = new TimeSpan(0,0,30);
TimeSpan SecondsZero = new TimeSpan(0);
TimeSpan waitTime = Second30 - SecondsZero;
TimeSpan interval;
while(1)
{
// Wait for work method to signal.
if(autoEvent.WaitOne(waitTime, false))
{
// Signalled time to quit
return;
}
else
{
stopWatch.Start();
// grab a lock
// do the work
// Whatever...
stopwatch.stop();
interval = stopwatch.Elapsed;
if (interval < Seconds30)
{
waitTime = Seconds30 - interval;
}
else
{
waitTime = SecondsZero;
}
}
}
}
两者之一的优点是,您可以通过发出信号来关闭线程。
编辑
我应该补充说明的是,这段代码假定您只运行一个MyWorkerThreads(),否则它们将会并发运行。
当我想要单一执行时,我使用了互斥锁:
private void OnMsgTimer(object sender, ElapsedEventArgs args)
{
// mutex creates a single instance in this application
bool wasMutexCreatedNew = false;
using(Mutex onlyOne = new Mutex(true, GetMutexName(), out wasMutexCreatedNew))
{
if (wasMutexCreatedNew)
{
try
{
//<your code here>
}
finally
{
onlyOne.ReleaseMutex();
}
}
}
}