Team LiB
Previous Section Next Section

Applying the lock Keyword

One of the simpler synchronization techniques to employ is the lock keyword, which offers an easy way to protect a resource. Similar to the Monitor class, the lock keyword employs the critical section technique to protect a resource. The lock keyword takes one parameter:

lock(object obj)

This parameter is an object and as such must be a reference type, not a value type. To protect a resource using the lock keyword, simply wrap the code in a lock block. The following code demonstrates how to lock a section of code:

lock(this)
{
  // Resource Protected Code
  counterVariable++;
}

Raising Thread Events

Introduced in Win32, events were used to synchronize threads by signaling other threads that it was okay to proceed with processing. This scheme is typically used when one thread needs to wait on another one to complete its task before proceeding. In .NET, two classes have been introduced to handle events: AutoResetEvent and ManualResetEvent. Both classes do exactly the same thing, with one exception. AutoResetEvent resets the signaled state of the event to unsignaled when another waiting thread is released. In contrast, ManualResetEvent does exactly what its name implies: It waits until the event is manually reset before changing the signaled state to unsignaled. To wait for the event to be signaled, either the WaitOne, WaitAny, or WaitAll method must be called. After any one of these methods has been called, the calling thread is placed in the WaitSleepJoin state and is blocked until the event is signaled or a timeout occurs. Listing 9.3 demonstrates how to wait on an event to occur before proceeding .

Listing 9.3. Waiting for an Event
using System;
using System.Threading;

namespace EventClass
{
  public class ClassCounter
  {
    protected int m_iCounter = 0;

    public void Increment()
    {
      m_iCounter++;
    }

    public int Counter
    {
      get
      {
        return m_iCounter;
      }
    }
  }

  public class EventClass
  {
    protected ClassCounter m_protectedResource = new ClassCounter();
    protected ManualResetEvent m_manualResetEvent = new ManualResetEvent(false);
    protected void ThreadOneMethod() 
    {
      m_manualResetEvent.WaitOne();
      m_protectedResource.Increment();
      int iValue = m_protectedResource.Counter;
      System.Console.WriteLine(
        "{Thread One} - Current value of counter: " + iValue.ToString());
    }

    protected void ThreadTwoMethod()
    {
      int iValue = m_protectedResource.Counter;
      System.Console.WriteLine(
        "{Thread Two} - Current value of counter: " + iValue.ToString());
      m_manualResetEvent.Set();
    }

    [STAThread]
    static void Main(string[] args)
    {
      EventClass exampleClass = new EventClass();

      Thread threadOne = new Thread(new ThreadStart(exampleClass.ThreadOneMethod));
      Thread threadTwo = new Thread(new ThreadStart(exampleClass.ThreadTwoMethod));

      threadOne.Start();
      threadTwo.Start();
      System.Console.ReadLine();
    }
  }
}

You should run the code in Listing 9.3 to get a good idea for how it works. In short, the first thread will wait for the second thread to signal once (using WaitOne) manually before it continues. Therefore, the second thread will report a lower value than the first thread.

Using the Mutex Class

Mutex is a special synchronization class that not only allows threads to synchronize, but also allows synchronization to take place across processes. Like most synchronization schemes, it allows only one thread to access a resource at a time. Mutex has four constructors:

Mutex aMutex = new Mutex();
Mutex aMutex = new Mutex(bool initiallyOwned);
Mutex aMutex = new Mutex(bool initiallyOwned, string name);
Mutex aMutex = new Mutex(bool initiallyOwned, string name, out bool createdNew);

Each successive constructor builds upon the previous constructor's parameters. The first constructor does not take any parameters and creates an unnamed mutex that does not acquire ownership to that mutex. The second constructor adds a Boolean parameter that indicates whether or not the mutex should acquire ownership upon construction. The third constructor introduces the concept of naming a mutex. This is useful when creating a mutex for interprocess synchronization. Finally, the fourth constructor adds a Boolean parameter that indicates whether or not ownership of the mutex was successfully acquired after construction. This parameter is an out parameter and, as such, is passed uninitialized. The following example demonstrates how to use a mutex to guarantee that only one instance of an application can exist at a time. To create a single-instance application, we must first create a mutex:

Mutex singleInstanceMutex = new Mutex(true, "Single Instance Application", out isOwned);

After the mutex has been created, the code must check whether the isOwned property is set to true. If it is set to true, you know that there are no other instances of this application running and you can continue to start the application. If the isOwned property is set to false, you know that another instance is running and you must therefore terminate this new instance. Listing 9.4 demonstrates how to create a single-instance application by placing the mutex construction in the Main section of the code.

Listing 9.4. Single Instance Application Using a Mutex Class
 [STAThread]
static void Main()
{
  bool isOwned;
  // The next line assumes that System.Threading is declared in the using section.
  Mutex singleInstanceMutex = 
    new Mutex(true, "Single Instance Application", out isOwned);
  try
  {
    if(isOwned)
      Application.Run(new Form1());
  }
  finally
  {
    if(isOwned)
      singleInstanceMutex.ReleaseMutex();
  }
}

As shown in the previous example, the mutex can be used to determine whether another thread or process owns the given mutex. If another thread or process owns the mutex, the code can assume that the mutex is already being used. You might be wondering what your code would do if it needs to wait for mutex ownership to be released. Fortunately, .NET adds a method named Mutex.WaitOne to the Mutex class. Mutex.WaitOne is an overloaded method that has three forms:

public virtual void Mutex.WaitOne();


public virtual bool Mutex.WaitOne(int millisecondTimeout, bool exitContext);


public virtual bool Mutex.WaitOne(TimeSpan timeout, bool exitContext);

The millisecondTimeout parameter specifies the time the thread should wait to receive a signal. The exitContext parameter specifies whether the thread should first exit a synchronization context before trying to reacquire it. This essentially means that if the thread currently owns the context, it should release it first, which could allow another thread to take ownership, and then wait until it can reacquire ownership before proceeding. The timeout parameter is the same as millisecondTimeout, except it uses a TimeSpan class instead of raw milliseconds, making for easier-to-read code. Listing 9.5 demonstrates how to wait until it is safe to access a mutex-protected resource.

Listing 9.5. Waiting on a Mutex
   Mutex testMutex = new Mutex(false, "Test Mutex");
   try
   {
 // Wait until a signal is received before moving on.
     testMutex.WaitOne();
   }
   finally
   {
   //  We own the Mutex, so we must release it.
     testMutex.ReleaseMutex();
   }

Using the Monitor Class

The capability for a process to restrict or lock access to a section of code is commonly known as a critical section. The Monitor class gives you the ability to use critical sections to control access to a section of code by granting a lock to a single thread. Similar to the lock keyword, the Monitor class provides a simple mechanism to lock a section of code. Simply wrap the code to be protected in a Monitor.Enter / Monitor.Exit code block, as illustrated in Listing 9.6. Both the Monitor.Enter and the Monitor.Exit methods take one parameter.

Monitor.Enter(object obj);
Monitor.Exit(object obj);

This parameter is the object in which to acquire the Monitor lock. Because the Monitor class is used to lock objects, this parameter must be a reference type, not a value type.

Listing 9.6. Using the Monitor Class
using System;
using System.Threading;

namespace SimpleMonitorClass
{
  public class ClassCounter
  {
    protected int m_iCounter = 0;

    public void Increment()
    {
      m_iCounter++;
    }

    public int Counter
    {
      get
      {
        return m_iCounter;
      }
    }
  }

  public class SimpleMonitorClass
  {
    protected ClassCounter m_protectedResource = new ClassCounter();

    protected void IncrementProtectedResourceMethod() 
    {
      Monitor.Enter(m_protectedResource);
      try
      {
        m_protectedResource.Increment();
      }
      finally
      {
        Monitor.Exit(m_protectedResource);
      }
    }

    [STAThread]
    static void Main(string[] args)
    {
      SimpleMonitorClass exampleClass = new SimpleMonitorClass();
      exampleClass.IncrementProtectedResourceMethod();
    }
  }
}

Using the Monitor class in the fashion illustrated in Listing 9.6 is essentially the same thing as using the lock keyword. However, unlike the lock keyword, the Monitor class also employs the tryEnter, Wait, Pulse, and PulseAll methods. The difference between the tryEnter and the Enter methods is simple: The Enter method waits indefinitely for the lock to the protected section to be released before returning, whereas the tryEnter method returns immediately, regardless of whether the lock was acquired. The Monitor.TryEnter method is an overloaded function that has three forms:

static bool Monitor.TryEnter(object obj);


static bool Monitor.TryEnter(object obj, int millisecondTimeout);


static bool Monitor.TryEnter(object obj, TimeSpan timeout);

The obj parameter is the same as the one used in the Monitor.Enter method. millisecondTimeout is the time in milliseconds to wait to acquire the lock before returning. The same holds true for the timeout parameter, except that it is the time specified by the TimeSpan object. On all three methods, the Boolean return value indicates whether the lock was acquired before returning. The following code fragment illustrates the use of the Monitor.TryEnter method:

protected void IncrementProtectedResourceMethod()
{
  if(Monitor.TryEnter(m_protectedResource) == true)
  {
    try
    {
      m_protectedResource.Increment();
    }
    finally
    {
      Monitor.Exit(m_protectedResource);
    }
  }
}

Both the Monitor.Pulse and the Monitor.PulseAll methods take threads that are waiting on the protected resource and are in the WaitSleepJoin state and place them in the Started state. This allows those threads to prepare to continue executing after the lock has been acquired. Along with the Monitor.Pulse and Monitor.PulseAll methods is the Monitor.Wait method. This method allows a thread to release a lock, if one has already been acquired, and to wait until another thread signals that it has completed its processing using the protected resource. The Monitor.Wait method has five overloaded methods:

static bool Monitor.Wait(object obj);


static bool Monitor.Wait (object obj, int millisecondTimeout);


static bool Monitor.Wait (object obj, TimeSpan timeout);


static bool Monitor.Wait (object obj, int millisecondTimeout, bool exitContext);


static bool Monitor.Wait (object obj, TimeSpan timeout, bool exitContext);

The first three methods have identical parameters to the Monitor.TryEnter method and function the same way. The last two methods take an additional Boolean parameter called exitContext. This parameter indicates whether to exit the synchronization context before attempting to reacquire it. Listing 9.7 demonstrates how to properly use the Monitor.Wait and Monitor.PulseAll methods.

Listing 9.7. Waiting and Pulsing Using the Monitor Class
using System;
using System.Threading;

namespace MonitorPulseClass
{
  public class ClassCounter
  {
    protected int m_iCounter = 0;

    public void Increment()
    {
      m_iCounter++;
    }

    public int Counter
    {
      get
      {
        return m_iCounter;
      }
    }
  }

  public class MonitorPulseClass
  {
    protected ClassCounter m_protectedResource = new ClassCounter();

    protected void ThreadOneMethod()
    {
      lock(m_protectedResource)
      {
        Monitor.Wait(m_protectedResource);
        m_protectedResource.Increment();
        int iValue = m_protectedResource.Counter;
        System.Console.WriteLine(
          "{Thread One} - Current value of counter: " + iValue.ToString());
      }
    }

    protected void ThreadTwoMethod()
    {
      lock(m_protectedResource)
      {
        int iValue = m_protectedResource.Counter;
        System.Console.WriteLine(
          "{Thread Two} - Current value of counter: " + iValue.ToString());
        Monitor.PulseAll(m_protectedResource);
      }
    }

    [STAThread]
    static void Main(string[] args)
    {
      MonitorPulseClass exampleClass = new MonitorPulseClass();

      Thread threadOne = new Thread(new ThreadStart(exampleClass.ThreadOneMethod));
      Thread threadTwo = new Thread(new ThreadStart(exampleClass.ThreadTwoMethod));

      threadOne.Start();
      threadTwo.Start();
      System.Console.ReadLine();
    }
  }
}

As shown in Listing 9.7, the Monitor.Wait and Monitor.PulseAll methods are wrapped in a lock block. This is necessary because these methods, along with the Monitor.Pulse method, must be called from a synchronized section of code or an exception will be thrown.

Safeguarding Variables (Interlocked Increment/Decrement)

For simple operations such as incrementing, decrementing, or exchanging values of a variable, .NET introduces the Interlocked class. This class prevents synchronization problems by allowing these operations to take place as an atomic operation. It is also a handy shortcut and cuts down on the amount of synchronized code that you have to write if all you need to do is perform simple operations in a synchronized context. The following code fragment demonstrates how to use this class:

public void DemonstrateInterlocked()
{
  // Increment value 1
  Interlocked.Increment(ref m_integerValue1);
  // Decrement value 2
  Interlocked.Decrement(ref m_integerValue2);
  // Sets variable 1 to the value of variable 2 and returns the original value.
  int orignalValue = Interlocked.Exchange(ref m_integerValue1, m_integerValue2);
}

To check whether a value is the same before exchanging, use the Interlocked.CompareExchange method instead.

Reading Without Waiting (ReaderWriterLock)

Synchronization problems arise when one thread is in the process of changing a variable's value at the same time that another thread is either trying to do the same or is reading the value. This can cause unpredictable results and is generally a bad thing. Most of the other synchronization techniques discussed in this chapter prevent this by blocking a thread from performing an operation while another thread is executing the section of code that is being protected. If all these threads are simply reading the protected values, the code is blocking the other threads needlessly. To overcome this challenge, .NET uses the ReaderWriterLock class. This enables us to block other threads only when a protected variable is being updated. Listing 9.8 demonstrates the use of the ReaderWriterLock class.

Listing 9.8. Reading a Value Without Waiting
using System;
using System.Threading;

namespace ReaderWriterClass
{
  public class ReaderWriterClass
  {
    protected ReaderWriterLock m_rwLock = new ReaderWriterLock();
    protected int m_counter = 0;
    protected int m_readerBlocks = 0;
    protected int m_writerBlocks = 0;

    protected void ThreadOneMethod()
    {
      for(int i=0; i<200; i++)
      {
        try
        {
          m_rwLock.AcquireReaderLock(0);
          try
          {
            System.Console.WriteLine(m_counter);
          }
          finally
          {
            m_rwLock.ReleaseReaderLock();
          }
        }
        catch(Exception)
        {
          Interlocked.Increment(ref m_readerBlocks);
        }
      }
    }

    protected void ThreadTwoMethod()
    {
      for(int i=0; i<100; i++)
      {
        try
        {
          m_rwLock.AcquireWriterLock(0);
          try
          {
            Interlocked.Increment(ref m_counter);
          }
          finally
          {
            m_rwLock.ReleaseWriterLock();
          }
        }
        catch(Exception)
        {
          Interlocked.Increment(ref m_writerBlocks);
        }

        Thread.Sleep(1);
      }
    }

    public int ReaderBlocks
    {
      get
      {
        return m_readerBlocks;
      }
    }

    public int WriterBlocks
    {
      get
      {
        return m_writerBlocks;
      }
    }

    [STAThread]
    static void Main(string[] args)
    {
      ReaderWriterClass exampleClass = new ReaderWriterClass();

      Thread threadOne = new Thread(new ThreadStart(exampleClass.ThreadOneMethod));
      Thread threadTwo = new Thread(new ThreadStart(exampleClass.ThreadTwoMethod));

      threadOne.Start();
      threadTwo.Start();

      // Wait for both threads to finish before proceeding.
      threadOne.Join();
      threadTwo.Join();
      // Print out the results
      System.Console.WriteLine(
        "Reader Blocks {0}, Writer Blocks {1}" , 
        exampleClass.ReaderBlocks, exampleClass.WriterBlocks);
      System.Console.ReadLine();
    }
  }
}

    Team LiB
    Previous Section Next Section