我需要在一定的时间间隔内进行一些操作(例如,从 5 到 5 分钟循环),但需要能够在我想要的任何时候完全停止事情(按下按钮)。
我正在考虑使用Timer类,但即使在计时器停止后事件也可能会触发。
我怎样才能让一些代码在计时器上运行,并且仍然能够立即使一切完全停止?
就像我正确理解的那样:完全停止是指事件停止并且我可以处理计时器本身等对象。我不是在问如何避免在计时器停止后触发的意外事件产生副作用!
这个问题的答案很大程度上取决于您的操作类型。
最好的方案是运行一个带有循环的线程并监听 abort 事件。
static AutoResetEvent abort = new AutoResetEvent();
Thread worker = new Thread(WorkerThread);
void MainThread()
{
worker.Start();
Thread.Sleep(30000);
abort.Set();
}
void WorkerThread()
{
while(true)
{
if(abort.WaitOne(5000)) return;
// DO YOUR JOB
}
}
当您abort.Set()
从另一个线程调用时,这个线程将退出。
但是,如果您的代码长时间运行,则在工作完成之前您将无法退出。要立即退出,您将不得不中止线程,但由于资源消耗,这不太明智。
或者,如果您的操作长时间运行(假设您正在处理长数组),您可以像这样不时检查“中止”事件状态(例如,每次循环迭代)abort.WaitOne(0)
。
计时器的竞争条件是不可避免的,因为正如您所说,回调是从线程池执行的。但是,我相信即使它仍在执行事件,您也可以安全地处置计时器。一个可能有帮助的选项是如果您考虑使用System.Threading.Timer
而不是,如果您需要知道计时器事件何时完成执行System.Timers.Timer
,您可以调用Timer.Dispose(WaitHandle) 。这将防止在您还需要处理一些其他资源的情况下出现竞争条件 - 事件使用者函数将尝试使用的资源。
至于“立即”要求,最直接的可能是使用某种同步原语来停止执行的东西。例如考虑这个:
static System.Timers.Timer timer;
static void Main(string[] args)
{
var cancelSource = new CancellationTokenSource();
timer = new System.Timers.Timer(200);
timer.Elapsed += new SomeTimerConsumer(cancelSource.Token).timer_Elapsed;
timer.Start();
// Let it run for a while
Thread.Sleep(5000);
// Stop "immediately"
cancelSource.Cancel(); // Tell running events to finish ASAP
lock (timer)
timer.Dispose();
}
class SomeTimerConsumer
{
private CancellationToken cancelTimer;
public SomeTimerConsumer(CancellationToken cancelTimer)
{
this.cancelTimer = cancelTimer;
}
public void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
lock (timer)
{
// Do some potentially long operation, that respects cancellation requests
if (cancelTimer.IsCancellationRequested)
return;
// More stuff here
}
}
}
这是一个玩具示例,但它说明了我的观点。执行“立即停止”的 3 行具有以下特点:
Dispose
调用返回时,任何// More stuff here
代码都不会再次执行。// More stuff here
代码可以执行。Note: if you need multiple timer events to execute simultaneously, consider using a ReaderWriterLockSlim
instead of a monitor.
我会考虑以下两种选择之一:
对需要执行的事件进行安全检查。类似于数据库标志的东西。因此,即使 Timer 未能停止,事件也会在安全检查失败时退出。
使用Quartz.Net 之类的东西进行调度。这真的很重,但它会做你想做的事。