2

我目前正在使用 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 中的剩余时间以确定计时器是否真的停止了。

4

1 回答 1

3

首先也是最重要的:公开公开锁定对象是个坏主意!

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);
}

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

于 2012-11-26T19:44:09.020 回答