1

我需要在一定的时间间隔内进行一些操作(例如,从 5 到 5 分钟循环),但需要能够在我想要的任何时候完全停止事情(按下按钮)。

我正在考虑使用Timer类,但即使在计时器停止后事件也可能会触发。

我怎样才能让一些代码在计时器上运行,并且仍然能够立即使一切完全停止?

就像我正确理解的那样:完全停止是指事件停止并且我可以处理计时器本身等对象。我不是在问如何避免在计时器停止后触发的意外事件产生副作用!

4

3 回答 3

3

这个问题的答案很大程度上取决于您的操作类型。

最好的方案是运行一个带有循环的线程并监听 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)

于 2012-10-31T18:28:51.393 回答
1

计时器的竞争条件是不可避免的,因为正如您所说,回调是从线程池执行的。但是,我相信即使它仍在执行事件,您也可以安全地处置计时器。一个可能有帮助的选项是如果您考虑使用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.

于 2012-10-31T19:27:36.250 回答
0

我会考虑以下两种选择之一:

  1. 对需要执行的事件进行安全检查。类似于数据库标志的东西。因此,即使 Timer 未能停止,事件也会在安全检查失败时退出。

  2. 使用Quartz.Net 之类的东西进行调度。这真的很重,但它会做你想做的事。

于 2012-10-31T18:23:05.223 回答