views:

153

answers:

8

Is there a better way to do implement a simple lock like below?

I only want to to the "DOSOMETHING" if it's not already being run. Should I be using reall locks here? if I use lock will that cause everything to queue up and wait for the lock to release? (that's not what I want!)

Thanks

  bool running = false;

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

        //DOSOMETHING

        this.running = false;
     }
 }
+1  A: 

The best way is to use a try/finally block

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

Real thread locks are only needed if this method is called from multiple threads. Given that it appears to be a paint event handler this is unlikely as controls are affinitized to a single thread.

JaredPar
+5  A: 

No, you do not want to use locks here. This is not a thread synchronization problem. This is a method reentrancy problem.

You might try something like this.

bool running = false; 

void DataDisplayView_Paint(object sender, PaintEventArgs e) 
{ 
  if (!this.running)
  {
    this.running = true; 
    try
    {
      //DOSOMETHING 
    }
    finally
    {
      this.running = false; 
    }
  }
}
Brian Gideon
The problem is that two threads could get through the check if(!running) before the first manages to set running=true. In this case you end up with 2 threads running DOSOMETHING at the same time.
Grzenio
@Grzenio: True, but the question never suggested that multiple threads where involved and the signature of the method in question seems to confirm that. This is none other than a `Control.Paint` event executing on a single thread...the UI thread. Given that I think it is more safe to assume that the OP was describing a reentrancy problem as opposed to a thread synchronization problem.
Brian Gideon
@Brian: fair point
Grzenio
Brian, I don't get it. Is there a way to have two simultaneous calls of the same method other then calling it twice from different threads?
Dmitry Ornatsky
@Dmitry: Yes, depending on the actions taken in a UI thread event handler. Some actions might cause the same event to be raised again synchronously or asynchronously. The `Paint` event is particularly pernicious in this regard because it is so easy to replicate. Just put a call to `MessageBox.Show` in there and see what happens. Make sure you save everything first as all hell may break lose on your computer :)
Brian Gideon
@Dmitry: There's another way also: **recursion** (and I don't mean explicitly calling `DataDisplayView_Paint`, but rather calling some methods on the control in question which raise the `Paint` event again, triggering this code a 2nd, 3rd, 4th, etc. time).
Dan Tao
@Dan, oh yeah, I haven't thought of indirect recursion, thanks a lot.
Dmitry Ornatsky
@Dan: Yeah, that's what I meant by synchronously. It can also occur asynchronously in that another message pump is started inside the event handler (`ShowDialog`, `DoEvents`, etc).
Brian Gideon
+2  A: 

You just need to synchronise (lock is the simplest way) bits of the code:

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;
          }
        }
     }
 }
Grzenio
DOSOMETHING methods tend to throw ;)
Dmitry Ornatsky
I always make my lock objects 'readonly'.
row1
Added try/finally and readonly, cheers.
Grzenio
@ grzenio, will this cause queuing up waiting for padlock to become free?
AidanO
@AidanO, you only need the lock to make sure that the check and assignment or running are done atomically, there is not really going to be any real waiting here.
Grzenio
A: 

If you use Monitor.TryEnter instead you could specify a timeout, in which case the result you get is such that:

  • only one thread can run the DOSOMETHING at a time
  • subsequent calls will try to get the lock and give up after the timeout clause

If you don't provide with a timeout, or set the timeout to 0, this call won't block and will return immediately (maybe that'd suit your requirement better?):

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

Alternatively, you can make the running variable volatile so that you will always get the latest value stored in the variable:

private volatile bool running;

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

The second approach won't queue up subsequent calls, but there is the possibility that two threads will both hit #1 and evaluate that it's safe to proceed then both end up running DOSOMETHING, though it's highly unlikely.

theburningmonk
A: 

You shift varaible names in the middle, so I'm going to assume you wanted:

  bool running = false; 

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

        //DOSOMETHING 

        this.running = false; 
     } 
 }

The problem you have here is that if DataDisplayView_Paint can be called from multiple threads, then it is possible that between the if (!this.running) and the this.running = true; the other thread could jump in and start DOSOMETHING (because running is still false). Then the first thread will resume, and start DOSOMETHING again. If that is a possiblity, then you will need to use a real lock.

James Curran
+1  A: 

Am I missing something? The code as you've posted it does not seem to do anything. That is, the code will run whether or not running is true.

Generally, any code that tries to "lock" itself like this...

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

...is perfectly good, as long as you're in a single-threaded scenario. If you're running multi-threaded code, problems can arise because you are assuming that no two threads will reach the if (!running) line at the same time.

The solution in multi-threaded code is to use some form of atomic switch. I've used the AutoResetEvent for this purpose:

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();
    }
}
Dan Tao
Thanks dan, well spotted, I'll edit the code
AidanO
+1  A: 

Note that if you're having reentrancy on your paint callback, you've got a more serious problem. Paint handlers should be blocking your message pump (and should complete relatively quickly), so you should never see this case. The only exception is if you call Application.DoEvents() from somewhere in your paint handler, which you really shouldn't be doing.

Dan Bryant
good spot Dan, maybe the paint was a poor example.
AidanO
A: 

I only want to to the "DOSOMETHING" if it's not already being run

Your question doesn't have enough information, so I can't help but make assumptions about your code.

  • My first assumption is that, based on the signature DataDisplayView_Paint(object s, PaintEventArgs e), your code runs on the GUI thread.

  • My second assumption is that your code DOSOMETHING is synchronous.

With that in mind, here's version of your code which guarantees we only run DOSOMETHING if its not already being run:

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

The GUI thread will only process one message at a time, and your DataDisplayView_Paint method does not exit until DOSOMETHING completes. If you're doing anything with the GUI like drawing to a Graphics object or changing labels, then this code won't get invoked from more than one thread -- and if it does, .NET will throw an exception. In other words, you don't need any synchronization.


Let's assume DOSOMETHING runs asyncronously -- now we have an interesting problem, but its very easy to solve, and you don't need any bools.

Essentially, all you're doing is disabling your event handler while DOSOMETHING runs, then re-enabling it. Instead of using a bool, unhook and rehook your event handler as needed:

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