【问题标题】:Synchronising access to state variable which is reset by a timer同步访问由计时器重置的状态变量
【发布时间】:2012-11-27 23:01:22
【问题描述】:

我目前正在使用 C# 开发一个项目。我正在使用单个锁同步对状态变量的访问。触发此状态变量以设置给定时间段,然后应重置其值。我目前的代码如下。

using System.Threading;

class Test
{
  object syncObj = new object();
  bool state = false;
  Timer stateTimer;

  Test()
  {
    stateTimer = new Timer(ResetState, this, Timeout.Infinite, Timeout.Infinite);
  }

  void SetState()
  {
    lock(syncObj)
    {
      state = true;
      stateTimer.Change(1000, Timeout.Infinite);    
    }
  }

  static void ResetState(object o)
  {
    Test t = o as Test;
    lock(t.syncObj)
    {
      t.state = false;
    }
  }  
}

鉴于在 Timer 调用 ResetState 之前再次调用 SetState 是有效的(即允许延长状态为 true 的时间段),我可以想象单个锁可能不够用的情况。我想到的具体情况是这样的

  • SetState 和 ResetState 是同时进入的,分别在主线程和 Timer 线程上
  • SetState 先获取锁,正确设置 state 为 true 并触发定时器重新启动
  • ResetState 然后错误地将 state 设置为 false,这意味着在预期的时间段内 state 不是 true

我一直在为这个问题摸不着头脑。我最接近能够解决的方法是使用两个锁,但最后我发现这导致了其他问题(至少,我这样做的方式)。

是否有解决此问题的已知方法(我是否应该阅读一些东西来更新我的同步知识)?

更新:我忘了提到在这种情况下无法查询计时器的当前状态。如果可以的话,我会想象检查 ResetState 中的剩余时间以确定计时器是否真的停止了。

【问题讨论】:

    标签: c# timer synchronization


    【解决方案1】:

    首先:it's a bad idea to expose the locking object publicly!

    class Test
    {
      private object syncObj = new object();
      private bool state = false;
      private Timer stateTimer;
    
      public Test()
      {
        stateTimer = new Timer(ResetState, this, Timeout.Infinite, Timeout.Infinite);
      }
    
      public void SetState()
      {
        lock(syncObj)
        {
          state = true;
          stateTimer.Change(1000, Timeout.Infinite);    
        }
      }
    
      public static void ResetState(object o)
      {
        Test t = o as Test;
        t.ResetState();
      }  
    

    由于您不再公开锁定对象,因此您必须创建另一种方法来重置状态:

      public void ResetState()
      {
        lock(syncObj)
        {
          state = false;
          stateTimer.Change(Timeout.Infinite, Timeout.Infinite);
        }
      }  
    
    
    }
    

    请注意,我们还在新的ResetState 方法中处理了另一个问题,那就是强制计时器不再触发。这只会保证state 标志不会与计时器不同步;即,如果您设置状态,它将在预期的时间内保持设置状态或直到调用重置方法。

    更新

    如果要拒绝重置尝试,则将状态变量设为枚举:

    enum EState
    {
        Off = 0,
        On = 1,
        Waiting = 2
    }
    
    private EState state = EState.Off;
    
    // Provide a state property to check if the state is on or of (waiting is considered to be Off)
    public bool State{ get{ return state == EState.On;} }
    

    此外,您现在需要修改 SetState 方法,并且您将需要两个重置方法(私有的将与计时器一起使用)。

    public void SetState()
    {
        lock(syncObj)
        {
            state = EState.Waiting;
            stateTimer.Change(1000, Timeout.Infinite);
        }
    }
    
    public void ResetState()
    {
        lock(syncObj)
        {
            if(state != EState.Waiting)
            {
                state = EState.Off;
            }
        }
    }
    
    private void TimerResetState()
    {
        lock(syncObj)
        {
            state = EState.Off;
            stateTimer.Change(Timeout.Infinite, Timeout.Infinite);
        }
    }
    

    所以现在你的构造函数看起来像这样:

    public Test()
    {
        stateTimer = new Timer(TimerResetState, this, Timeout.Infinite, Timeout.Infinite);
    }
    

    事情应该大致按照这些思路进行。

    【讨论】:

    • P.S.请注意,我假设正确的行为是重置计时器,但如果您的目标是在计时器触发之前拒绝重置尝试,那么这也可以实现(尽管有点困难)。
    • 感谢 cmets Lirik。我完全同意公开公开锁定对象是一个坏主意 - 这不是我的意图,但我没有在我的代码中明确说明。我的目标确实是在计时器触发之前拒绝重置尝试。关于如何在此模型中实现这一点的任何想法?
    • @Andrew 我更新了我的答案,请查看更新以获取更多信息。
    猜你喜欢
    • 2020-12-20
    • 1970-01-01
    • 2021-08-01
    • 1970-01-01
    • 2019-07-07
    • 1970-01-01
    • 2016-09-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多