2

我在 CF 项目(Windows Embedded CE 6.0)、VS2005 C#、.NET 2.0 中使用 System.Threading.Timer。这个计时器是需要的,因为这样使用时不可能重入:

    private System.Threading.Timer mainTimer;

    private void MainForm_Load(object sender, EventArgs e)
    {
        // other initializations
        mainTimer = new System.Threading.Timer(new TimerCallback(timerMain_Tick),
                null, 100, Timeout.Infinite);
    }

也就是说,使用了dueTime参数,但没有使用period。只要 period 为 Timeout.Infinite,计时器只会触发一次。通过检查表单的 InvokeRequired 属性使计时器成为线程安全的。请注意检查是否为空。它与我的问题有关,我很快就会回答。

    private void timerMain_Tick(object stateInfo)
    {
        if (mainTimer != null)
        {
            if (this.InvokeRequired)
            {
                this.Invoke((ThreadStart)delegate
                {
                    TimerProcess();
                });
            }
            else
            {
                TimerProcess();
            }
        }
    }

计时器必须在退出之前自行重新启动。

    private void TimerProcess()
    {
        try
        {
                // do work here
        }
        finally
        {
            // retrigger
            mainTimer.Change(mainTimerInterval, Timeout.Infinite);
        }
    }

我遇到的问题是优雅地停止这个该死的事情。

    private void MainForm_Closing(object sender, CancelEventArgs e)
    {
        // shut down timer
        mainTimer.Change(Timeout.Infinite, Timeout.Infinite);
        mainTimer.Dispose();
        mainTimer = null;
    }

大约 10 次中的 3 次,无论如何都会触发计时器,并且我收到 Object Disposed 错误。计时器代码尝试在检查 null 之后调用计时器方法。我怀疑计时器触发了,并且它的线程在表单关闭时被挂起。我尝试了状态机枚举:

正常状态运行

Form_Closing 设置 Stopping 状态并在 Thread.Sleep() 循环中等待 Stopped 状态

Timer 看到 Stopping 并设置 Stopped 状态(而不是重新触发自身)

我遇到的问题是计时器线程不会抢占表单关闭方法,因此陷入无限循环。如何解决这个问题?请注意,在 CF 中,没有 Dispose(WaitHandle) 方法。

4

2 回答 2

3

有趣的问题。Compact Framework 中的 Timer 选项似乎并不多。

我不确定您的特定代码是如何工作的,因此添加单个静态布尔值可能会也可能不会解决您的问题。

以下是我如何更改您的代码以接受一个timerOK值。如果这不能解决您的问题,它可以为您提供有关如何解决此问题的想法。

private static bool timerOK;
private static long mainTimerInterval = 200;
private System.Threading.Timer mainTimer;

private void MainForm_Load(object sender, EventArgs e) {
  timerOK = true;
  mainTimer = new System.Threading.Timer(new TimerCallback(timerMain_Tick), null, 100, Timeout.Infinite);
}

private void MainForm_Closing(object sender, CancelEventArgs e) {
  timerOK = false;
  mainTimer.Change(Timeout.Infinite, Timeout.Infinite);
  mainTimer.Dispose();
  mainTimer = null;
}

private void timerMain_Tick(object stateInfo) {
  if (timerOK && (mainTimer != null)) {
    if (this.InvokeRequired) {
      this.Invoke((ThreadStart)delegate {
        TimerProcess();
      });
    } else {
      TimerProcess();
    }
  }
}

private void TimerProcess() {
  if (!timerOK) return;
  try {
    // do work here
  } finally {
    // retrigger
    mainTimer.Change(mainTimerInterval, Timeout.Infinite);
  }
}
于 2013-06-11T14:45:38.437 回答
1

我正在使用 MVP,所以我的布局有点不同,但基本上我有两个相同的问题需要解决:

  1. 在处理方法中的目标被处理后停止定时器触发
  2. 在处理期间停止计时器触发

第一个很容易修复,如上面 pwrgreg007 所示,只需在“关闭”过程中的某个时间(在处理表单目标之前)关闭计时器并将计时器归零,然后在计时器处理事件开始时进行空检查。

第二个问题有点棘手,即使计时器(和您的表单)在您的处理循环开始时运行,也没有什么能阻止它在进程中途关闭,因为它在不同的线程上运行。为了防止这种情况,我创建了一个在定时器执行和定时器关闭期间都使用的锁。

//simplified presenter
public class Presenter
{
    private const int REFRESH_TIME_MILLISECONDS = 5000;
    private view _view;
    private Timer _timer;
    private object _timerLock = new object();   

    //CTOR
    public Presenter()
    {   
        _view = new View();

        Startup();
    }

    //spin up presenter
    public void Startup(){

        //bind view shutdown event
        _view.ViewClosing += Shutdown;

        //start timer
        _timer = new Timer(DoTimerStuff, null, REFRESH_TIME_MILLISECONDS, Timeout.Infinite);
    }

    //spin down presenter
    public void Shutdown()
    {
        //wait for any DoTimerStuff locks to expire
        lock (_timerLock)
        {
            //stop the timer
            _timer.Change(Timeout.Infinite, Timeout.Infinite);
            _timer.Dispose();
            _timer = null;                
        }

        //close the view
        _view.Shutdown();
    }

    //timer tick
    private void DoTimerStuff(object state)
    {
        //grab a lock so we can ensure the timer doesn't get shutdown mid way through
        lock (_timerLock)
        {
            //make sure the timer isn't shutdown (from form closing)
            if (_timer == null) return;

            //do your stuff here
            _view.SomeInvokedCheckedProperty = "SomeValue";
            //etc...

            //schedule next timer execute (runs every runtime + refresh time)
            _timer.Change(REFRESH_TIME_MILLISECONDS, Timeout.Infinite);
        }
    }
}

//simplified view
public class View : Form
{

    //view properties (make sure they are invoke checked)
    public SomeInvokedCheckedProperty {get;set;}

    //Bound to ViewClosing
    private void View_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            //stop the view closing itself
            e.Cancel = true;

            //tell the presenter to handle closing instead
            if (ViewClosing != null) ViewClosing.Invoke();
        }
}

那样..

  • DoTimerStuff()如果有锁并且当前正在运行,计时器将等待关闭(保持表单关闭)
  • 相反,DoTimerStuff()如果计时器关闭有锁,它将等待,当它继续时,它将正确地看到计时器已关闭(并且什么都不做)。
于 2018-02-21T03:32:29.133 回答