我想知道有没有更好的方法来实现这个“简单的锁定”

时间:2010-08-24 15:11:44

标签: c# locking nonblocking

有没有更好的方法来实现像下面这样的简单锁定?

如果它还没有被运行,我只想要“DOSOMETHING”。我应该在这里使用reall锁吗?如果我使用锁定会导致一切排队并等待锁定释放? (那不是我想要的!)

由于

  bool running = false;

  void DataDisplayView_Paint(object sender, PaintEventArgs e)
  {
    // if (!this.initialSetDone)  
     if (!running)
     {
        this.running = true;

        //DOSOMETHING

        this.running = false;
     }
 }

8 个答案:

答案 0 :(得分:5)

不,你不想在这里使用锁。这不是线程同步问题。这是一种方法重入问题。

您可以尝试这样的事情。

bool running = false; 

void DataDisplayView_Paint(object sender, PaintEventArgs e) 
{ 
  if (!this.running)
  {
    this.running = true; 
    try
    {
      //DOSOMETHING 
    }
    finally
    {
      this.running = false; 
    }
  }
}

答案 1 :(得分:2)

您只需要同步(锁定是最简单的方法)位代码:

bool running = false;
readonly object padlock = new object();

  void DataDisplayView_Paint(object sender, PaintEventArgs e)
  {

     if (!this.initialSetDone)
     {
        lock(padlock)
        {
          if(running) return;
          running = true;
        }
        try {

          //DOSOMETHING
        }
        finally
        {
          lock(padlock)
          {
            this.running = false;
          }
        }
     }
 }

答案 2 :(得分:1)

最好的方法是使用try / finally块

try { 
  this.running = true;
  ...
} finally {
  this.running = false;
}

只有从多个线程调用此方法时才需要真正的线程锁。鉴于它似乎是一个paint事件处理程序,因此不太可能将控件与单个线程关联起来。

答案 3 :(得分:1)

我错过了什么吗?您发布的代码似乎没有做任何事情。也就是说,无论running是否为真,代码都会运行。

通常,任何尝试像这样“锁定”自己的代码......

if (!running)
{
    running = true;

    try
    {
        // This code should not call itself recursively.
        // However, it may execute simultaneously on more than one thread
        // in very rare cases.
    }
    finally
    {
        running = false;
    }
}

...非常好,只要你处于单线程场景中。如果您正在运行多线程代码,则会出现问题,因为您假设没有两个线程同时到达if (!running)行。

多线程代码中的解决方案是使用某种形式的原子开关。我已将AutoResetEvent用于此目的:

var ready = new AutoResetEvent(true);

if (ready.WaitOne(0))
{
    try
    {
        // This code will never be running on more than one thread
        // at a time.
    }
    finally
    {
        ready.Set();
    }
}

答案 4 :(得分:1)

请注意,如果您对油漆回调进行了重新考虑,那么您会遇到更严重的问题。油漆处理程序应该阻止你的消息泵(并且应该相对快速地完成),所以你永远不应该看到这种情况。唯一的例外是你从paint处理器的某个地方调用Application.DoEvents(),你真的应该这样做。

答案 5 :(得分:0)

如果您使用Monitor.TryEnter,则可以指定超时,在这种情况下,您获得的结果是:

  • 一次只能有一个线程运行DOSOMETHING
  • 后续调用将尝试获取锁定并在超时子句
  • 之后放弃

如果您没有提供超时,或将超时设置为0,则此呼叫不会阻止并立即返回(可能更符合您的要求?):

if (!this.initialSetDone && Monitor.TryEnter(_lock))
{
   // DOSOMETHING
}

或者,您可以将running变量设为volatile,以便始终获得存储在变量中的最新值:

private volatile bool running;

if (!this.initialSetDone && !this.running)  // #1
{
   this.running = true;
   try
   {
     // DOSOMETHING
   }
   finally
   {
     this.running = false;
   }
}

第二种方法不会排队后续调用,但是有可能两个线程都会命中#1并评估它是否可以继续运行然后最终都运行DOSOMETHING,尽管它不太可能。

答案 6 :(得分:0)

你在中间移动了可变名称,所以我假设你想要:

  bool running = false; 

  void DataDisplayView_Paint(object sender, PaintEventArgs e) 
  { 
     if (!this.running) 
     { 
        this.running = true; 

        //DOSOMETHING 

        this.running = false; 
     } 
 }

你遇到的问题是,如果可以从多个线程调用DataDisplayView_Paint,那么if (!this.running)this.running = true;之间的另一个线程可能会跳入并启动DOSOMETHING(因为正在运行)仍然是假的)。然后第一个线程将恢复,并再次启动DOSOMETHING。如果这是可能的,那么你将需要使用真正的锁。

答案 7 :(得分:0)

  

我只想要“DOSOMETHING”   它尚未运行

您的问题没有足够的信息,所以我不禁对您的代码做出假设。

  • 我的第一个假设是,基于签名DataDisplayView_Paint(object s, PaintEventArgs e),您的代码在GUI线程上运行。

  • 我的第二个假设是您的代码DOSOMETHING是同步的。

考虑到这一点,这里的代码版本保证我们只运行DOSOMETHING,如果它尚未运行:

void DataDisplayView_Paint(object s, PaintEventArgs e)
{
    //DOSOMETHING
}

GUI线程一次只处理一条消息,DataDisplayView_Paint方法在DOSOMETHING完成之前不会退出。如果你正在使用GUI做任何事情,比如绘制到Graphics对象或更改标签,那么这个代码将不会从多个线程调用 - 如果是这样,.NET将抛出异常。换句话说,需要任何同步。


让我们假设DOSOMETHING异步运行 - 现在我们有一个有趣的问题,但它很容易解决,而且你不需要任何bool。

基本上,您所做的只是在DOSOMETHING运行时禁用事件处理程序,然后重新启用它。而不是使用bool,根据需要取消挂钩并重新挂钩您的事件处理程序:

void DataDisplayView_Paint(object s, PaintEventArgs e)
{
    DataDisplayView.Paint -= DataDisplayView_Paint;
    DoSomethingAsynchronously(); // re-hooks event handler when completed
}

void DoSomethingAsychronously()
{
    ThreadPool.QueueUserWorkItem(() =>
    {
        try
        {
            // DOSOMETHING
        }
        finally
        {
            // may need a lock around this statement
            DataDisplayView.Paint += DataDisplayView_Paint;
        }
    });
}