【问题标题】:Is there a better way to block a C# timer when interacting with the user?与用户交互时是否有更好的方法来阻止 C# 计时器?
【发布时间】:2009-12-08 09:17:18
【问题描述】:

我在应用程序的主窗体上使用System.Windows.Forms.Timer 组件,该组件每分钟检查一次数据库的更新。但是,与用户有许多可能的交互,例如为某些操作抛出模式对话框。一般来说,我不希望在用户“忙”或另一个数据库操作处于活动状态时触发此计时器。

我目前的方法是用 checkDatabaseTimer.Stop()/Start() 对明确地将此类“敏感”代码块括起来(使用 try/finally 以确保在退出时始终调用 Start)。

但这对我来说似乎很笨拙:如果未来的任何编码人员对此表单的代码进行修改,他们需要记住在任何代码周围应用这个停止/开始括号,例如,提示用户输入。忘记这样做似乎很容易。

有没有更好的方法来处理这个问题?或者这只是我们必须意识到的事情,因为多线程就像用剪刀跑,你可以假装不简单和安全的事情之一?

【问题讨论】:

    标签: c# multithreading timer


    【解决方案1】:

    第一个 System.Forms.Timer 不会在不同的线程中运行该操作 - 它只是向表单的消息循环发布一条消息,并且当它空闲时表单会处理计时器事件。

    因此,如果您的用户正在执行某个操作 - 计时器将“等待”直到操作结束;

    为了明确地使计时器“启动/停止”,我更喜欢使用在计时器运行方法开始时检查的布尔值。

    您可以使用帮助类来减少方法的“编码开销”:

    public class TimerEnableFlag
    {
        public bool IsTimerEnabled{get; set;}
    }
    
    public class TimerEnabler : IDisposable
    {
       private TimerEnableFlag flag;
    
       public TimerEnabler(TimerEnableFlag flag)
       {
           this.flag = flag;
           flag.IsTimerEnabled = true;
       }
    
       public void Dispose()
       {
          flag.IsTimerEnabled = false;
       }
    }
    

    现在您需要做的就是拥有一个“全局变量”,它保存您在进入计时器运行的方法时检查的计时器启用标志,当您想要禁用计时器时,只需添加以下代码:

    using(var enabler = new TimerEnabler(m_flag))
    {
        //...
    }
    

    【讨论】:

    • 啊,现在我很高兴我添加了一个组件计时器 - 看起来当我在数据库上做某事时我不需要太担心,就在我做某事的时候时髦的用户。我完全忘记了“使用”——简化控制块的好方法。
    • 代码示例中的轻微遗漏:TimerEnabler 需要持有对标志的引用,以便 Dispose() 可以更改它。
    【解决方案2】:

    不是 100% 清楚,但您可以使用 application events 做一些事情,具体取决于您想要阻止计时器的确切时间

    或者,您可以通过创建一个实现 IDisposable 的帮助器对象来减少每个调用站点的代码,以便以 RAII 样式启动/停止计时器,然后您可以将 using 块与此帮助器对象一起使用。

    public class Suspend : IDisposable
    { 
        private Timer timerRef;
        public Suspend(Timer timer)
        {   
            timerRef = timer
            timer.Stop();
        }
        public void Dispose()
        {
            timerRef.Start();
            //note we don't actually want to dispose the timer here
        }
    }
    
    using(var s = new Suspend(myTimer)
    {
        //do stuff
    }
    

    【讨论】:

      【解决方案3】:

      如果您希望计时器事件中的代码在对话框或其他执行其他操作时不运行,那么您可以在计时器中进行检查。不要让所有其他代码“知道计时器”,而是让计时器知道阻塞规则。

      如果消息框打开或者定时器所在的窗体拥有的窗体打开,这会阻止定时器。您可以添加它来处理您的特定案例:

      public bool BlockTimer()
      {
          //check if a form owned by this form is open
          foreach (Form f in Application.OpenForms) {
              if (object.ReferenceEquals(f.Owner, this)) {
                  //Another form open, dont run timer code
                  return true;
              }
          }
          //check if a messagebox or inputbox is open
          if (this.ActiveForm == null) {
              //Message box is active, dont run timer code
              return true;
          }
      
      
          return false;
      }
      

      在计时器事件中,只需在任何其他代码之前调用该函数即可:

      {
          if (BlockTimer() == true) return;
      
      }
      

      【讨论】:

      • 这是另一个有趣的想法。我不认为我会这样做,因为到目前为止它可能会将计时器扫到地毯下,我们可能会忘记它对 BlockTimer 进行更新。但这很好。
      【解决方案4】:

      使用单独的System.Timers.Timer 怎么样? System.Timers.Timer 在经过的事件中创建/使用线程池。此外,与 System.Windows.Forms.Timer 不同,它会在定义的时间间隔后准确触发。

      【讨论】:

      • 我并不在意确切的时间,我只想以最直接的方式阻止事件。正如 Dror 所指出的,如果应用程序很忙,Timer 组件将不会触发 - 只有在处理消息时才会触发,这意味着我需要担心的场景更少。
      猜你喜欢
      • 2021-05-08
      • 1970-01-01
      • 1970-01-01
      • 2013-01-11
      • 1970-01-01
      • 1970-01-01
      • 2011-10-27
      • 2014-04-07
      • 2017-11-02
      相关资源
      最近更新 更多