BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Parallel and Asynchronous
.NET 1.1+

Thread Synchronisation with the Monitor Class

The lock statement provides a simple way to block access to critical sections of code in multi-threaded and parallel software. When more flexibility is required, the members of the Monitor class can be used to achieve similar thread synchronisation.

Monitor.TryEnter

If the Monitor class only contained the Enter and Exit methods, it would provide little benefit over using lock statements. However, the class does include several other methods that are used for thread synchronisation. In this article we'll look at one other, TryEnter. We'll consider the other members in a future article.

As the name may suggest, TryEnter attempts to obtain a lock based upon an object. This works in the same manner as Enter, except that if a lock is unavailable, the thread will not be blocked indefinitely. In the most basic overloaded version of TryEnter, if the lock cannot be obtained, TryEnter will return false and execution will continue. If a lock is available, it is taken and the method returns true.

You can modify the behaviour of the program according to the result of TryEnter, avoiding accessing shared state if the method returns false. For example, if your thread has other jobs to do you could perform an alternative action before trying to obtain the lock for a second time. Alternatively, you could abort the operation, perhaps notifying the user that the action is temporarily unavailable and should be tried again later.

Let's update the code from the previous example to use TryEnter and report failure when a lock cannot be obtained, rather than waiting for the lock to become available. To do so, replace the code for the AddOne and SubtractOne methods with that shown below. The comment shows one possible outcome.

static void AddOne()
{
    if (Monitor.TryEnter(_lock))
    {
        try
        {
            int temp = _counter;
            temp++;
            Thread.Sleep(2000);
            Console.WriteLine("Incremented counter to {0}.", temp);
            _counter = temp;
        }
        finally { Monitor.Exit(_lock); }
    }
    else
    {
        Console.WriteLine("Could not increment counter.");
    }
}

static void SubtractOne()
{
    if (Monitor.TryEnter(_lock))
    {
        try
        {
            int temp = _counter;
            temp--;
            Thread.Sleep(2000);
            Console.WriteLine("Decremented counter to {0}.", temp);
            _counter = temp;
        }
        finally { Monitor.Exit(_lock); }
    }
    else
    {
        Console.WriteLine("Could not decrement counter.");
    }
}

/* OUTPUT

Could not decrement counter.
Incremented counter to 1.
Final counter value is 1.

*/

Using Timeouts

When you call TryEnter with no parameters, the method either obtains a lock and returns true, or fails to obtain the lock and returns false. In either case, control passes to the next command immediately. In some cases it is useful to block threads temporarily when the lock is unavailable, essentially adding a timeout to the operation and creating the possibility that the lock will be obtained later.

To add a timeout, you can provide an integer argument to TryEnter. This specifies the number of milliseconds that the thread may wait for a lock held by another thread to be released. Alternatively, you can provide a TimeSpan value to determine the timeout length. In either case, if a lock is obtained, execution continues immediately.

To demonstrate, change both of the if statements in the code to use the alternative below. This allows a thread to be blocked for up to three seconds before aborting the attempt to get the lock. This is long enough for the code to complete both thread's operations.

if (Monitor.TryEnter(_lock, TimeSpan.FromSeconds(3)))
30 September 2012