Chapter 21. Threads and Synchronization

Thread are responsible for multitasking within a single application. The System.Threading namespace provides a wealth of classes and interfaces to manage multithreaded programming. The majority of programmers might never need to manage threads explicitly, however, because the CLR abstracts much of the threading support into classes that simplify most threading tasks.

The first part of this chapter shows you how to create, manage, and kill threads. Even if you don’t create your own threads explicitly, you’ll want to ensure that your code can handle multiple threads if it’s run in a multithreading environment. This concern is especially important if you are creating components that other programmers might use in a program that supports multithreading.

The second part of this chapter focuses on synchronization. When you have a limited resource (such as a database connection) you may need to restrict access to that resource to one thread at a time. A classic analogy is to a restroom on an airplane. You want to allow access to the restroom for only one person at a time. You do this by putting a lock on the door. When passengers want to use the restroom, they try the door handle; if it is locked, they either go away and do something else, or wait patiently in line with others who want access to the resource. When the resource becomes free, one person is taken off the line and given the resource, which is then locked again.

At times, various threads might want to access a resource in your program, such as a file. It might be important to ensure that only one thread has access to your resource at a time, and so you will lock the resource, allow a thread access, and then unlock the resource. Programming locks can be fairly sophisticated, ensuring a fair distribution of resources.

Threads

Threads are typically created when you want a program to do two things at once. For example, assume you are calculating pi (3.141592653589 . . . ) to the 10 billionth place. The processor will happily begin to compute this, but nothing will write to the user interface while it is working. Because computing pi to the 10 billionth place will take a few million years, you might like the processor to provide an update as it goes. In addition, you might want to provide a Stop button so that the user can cancel the operation at any time. To allow the program to handle the click on the Stop button, you will need a second thread of execution.

Another common place to use threading is when you must wait for an event, such as user input, a read from a file, or receipt of data over the network. Freeing the processor to turn its attention to another task while you wait (such as computing another 10,000 values of pi) is a good idea, and it makes your program appear to run more quickly.

On the flip side, note that in some circumstances, threading can actually slow you down. Assume that in addition to calculating pi, you also want to calculate the Fibonacci series (1,1,2,3,5,8,13,21, . . . ). If you have a multiprocessor machine, this will run faster if each computation is in its own thread. If you have a single-processor machine (as most users do), computing these values in multiple threads will certainly run slower than computing one and then the other in a single thread because the processor must switch back and forth between the two threads. This incurs some overhead.

Starting Threads

The simplest way to create a thread is to create a new instance of the Thread class. The Thread constructor takes a single argument: a delegate instance. The CLR provides the ThreadStart delegate class specifically for this purpose, which points to a method you designate. This allows you to construct a thread and to say to it, “When you start, run this method.” The ThreadStart delegate declaration is:

public delegate void ThreadStart(  );

As you can see, the method you attach to this delegate must take no parameters and must return void. Thus, you might create a new thread like this:

Thread myThread = new Thread( new ThreadStart(myFunc) );

For example, you might create two worker threads, one that counts up from zero:

public void Incrementer(  )
{
    for (int i =0;i<1000;i++)
    {
       Console.WriteLine("Incrementer: {0}", i);
    }
}

and one that counts down from 1,000:

public void Decrementer(  )
{
    for (int i = 1000;i>=0;i--)
    {
        Console.WriteLine("Decrementer: {0}", i);
    }
}

To run these in threads, create two new threads, each initialized with a ThreadStart delegate. These in turn would be initialized to the respective member functions:

Thread t1 = new Thread( new ThreadStart(Incrementer) );

Thread t2 = new Thread( new ThreadStart(Decrementer) );

Instantiation of these threads doesn’t start them running. To do so, you must call the Start method on the Thread object itself:

t1.Start(  );

t2.Start(  );

Tip

If you don’t take further action, the thread stops when the function returns. You’ll see how to stop a thread before the function ends later in this chapter.

Example 21-1 is the full program and its output. You will need to add a using statement for System.Threading to make the compiler aware of the Thread class. Notice the output, where you can see the processor switching from t1 to t2.

Example 21-1. Using threads
using System;
using System.Threading;

namespace UsingThreads
{
    class Tester
    {
        static void Main(  )
        {
            // make an instance of this class
            Tester t = new Tester(  );

            Console.WriteLine("Hello");
            // run outside static Main
            t.DoTest(  );
        }

        public void DoTest(  )
        {
            // create a thread for the Incrementer
            // pass in a ThreadStart delegate

            // with the address of Incrementer
            Thread t1 = new Thread(
                new ThreadStart(Incrementer));

            // create a thread for the Decrementer
            // pass in a ThreadStart delegate
            // with the address of Decrementer
            Thread t2 = new Thread(
                new ThreadStart(Decrementer));

            // start the threads
            t1.Start(  );
            t2.Start(  );
        }

        // demo function, counts up to 1K
        public void Incrementer(  )
        {
            for (int i = 0; i < 1000; i++)
            {
                System.Console.WriteLine(
                    "Incrementer: {0}", i);
            }
        }

        // demo function, counts down from 1k
        public void Decrementer(  )
        {
            for (int i = 1000; i >= 0; i−−)
            {
                System.Console.WriteLine(
                    "Decrementer: {0}", i);
            }
        }
    }
}

Output (excerpt):
Incrementer: 102
Incrementer: 103
Incrementer: 104
Incrementer: 105
Incrementer: 106
Decrementer: 1000
Decrementer: 999
Decrementer: 998
Decrementer: 997

The processor allows the first thread to run long enough to count up to 106. Next, the second thread kicks in, counting down from 1,000 for a while. Then, the first thread is allowed to run. When I run this with larger numbers, I’ve noticed that each thread is allowed to run for about 100 numbers before switching.

Warning

The actual amount of time devoted to any given thread is handled by the thread scheduler and depends on many factors, such as the processor speed, demands on the processor from other programs, and so on.

Joining Threads

When you tell a thread to stop processing and wait until a second thread completes its work, you are said to be joining the first thread to the second. It is as though you tied the tip of the first thread onto the tail of the second, hence “joining” them.

To join thread 1 (t1) onto thread 2 (2), write:

t2.Join(  );

If this statement is executed in a method in thread t1, t1 will halt and wait until t2 completes and exits. For example, you might ask the thread in which Main( ) executes to wait for all your other threads to end before it writes its concluding message. In this next code snippet, assume you’ve created a collection of threads named myThreads. Iterate over the collection, joining the current thread to each thread in the collection in turn:

foreach (Thread myThread in myThreads)
{
    myThread.Join(  );
}

Console.WriteLine("All my threads are done.");

The final message, All my threads are done., isn’t printed until all the threads have ended. In a production environment, you might start up a series of threads to accomplish some task (e.g., printing, updating the display, etc.) and not want to continue the main thread of execution until the worker threads are completed.

Blocking Threads with Sleep

At times, you want to suspend your thread for a short while. You might, for example, like your clock thread to suspend for about a second in between testing the system time. This lets you display the new time about once a second without devoting hundreds of millions of machine cycles to the effort.

The Thread class offers a public static method, Sleep, for just this purpose. The method is overloaded; one version takes an int, the other a timeSpan object.

Each represents the number of milliseconds you want the thread suspended for, expressed either as an int (e.g., 2,000 = 2,000 milliseconds or two seconds), or as a timeSpan.

Although timeSpan objects can measure ticks (100 nanoseconds), the Sleep( ) method’s granularity is in milliseconds (1 million nanoseconds).

To cause your thread to sleep for one second, you can invoke the static method of Thread.Sleep, which suspends the thread in which it is invoked:

Thread.Sleep(1000);

At times, you’ll pass zero for the amount of time to sleep; this signals the thread scheduler that you’d like your thread to yield to another thread, even if the thread scheduler might otherwise give your thread a bit more time.

If you modify Example 21-1 to add a Thread.Sleep(1) statement after each WriteLine( ), the output changes significantly:

for (int i =0;i<1000;i++)
{
    Console.WriteLine(
    "Incrementer: {0}", i);
    Thread.Sleep(1);
}

This small change is sufficient to give each thread an opportunity to run once the other thread prints one value. The output reflects this change:

Incrementer: 0
Incrementer: 1
Decrementer: 1000
Incrementer: 2
Decrementer: 999
Incrementer: 3
Decrementer: 998
Incrementer: 4
Decrementer: 997
Incrementer: 5
Decrementer: 996
Incrementer: 6
Decrementer: 995

Killing Threads

Typically, threads die after running their course. You can, however, ask a thread to kill itself. The cleanest way is to set a KeepAlive Boolean flag that the thread can check periodically. When the flag changes state (e.g., goes from true to false), the thread can stop itself.

An alternative is to call Thread.Interrupt, which asks the thread to kill itself. Finally, in desperation, and if you are shutting down your application in any case, you may call Thread.Abort. This causes a ThreadAbortException exception to be thrown, which the thread can catch.

The thread ought to treat the ThreadAbortException exception as a signal that it is time to exit immediately. In any case, you don’t so much kill a thread as politely request that it commit suicide.

You might wish to kill a thread in reaction to an event, such as the user clicking the Cancel button. The event handler for the Cancel button might be in thread t1, and the event it is canceling might be in thread t2. In your event handler, you can call Abort on t1:

t2.Abort(  );

An exception will be raised in t1’s currently running method that t1 can catch.

In Example 21-2, three threads are created and stored in an array of Thread objects. Before the Threads are started, the IsBackground property is set to true (background threads are exactly like foreground threads, except that they don’t stop a process from terminating). Each thread is then started and named (e.g., Thread1, Thread2, etc.). A message is displayed indicating that the thread is started, and then the main thread sleeps for 50 milliseconds before starting up the next thread.

After all three threads are started, and another 50 milliseconds have passed, the first thread is aborted by calling Abort( ). The main thread then joins all three of the running threads. The effect of this is that the main thread will not resume until all the other threads have completed. When they do complete, the main thread prints a message: All my threads are done.. Example 21-2 displays the complete source.

Example 21-2. Interrupting a thread
using System;
using System.Threading;

namespace InterruptingThreads
{
   class Tester
    {
        static void Main(  )
        {
            // make an instance of this class
            Tester t = new Tester(  );

            // run outside static Main
            t.DoTest(  );
        }

        public void DoTest(  )
        {
            // create an array of unnamed threads
            Thread[] myThreads =
             {
                 new Thread( new ThreadStart(Decrementer) ),
                 new Thread( new ThreadStart(Incrementer) ),
                 new Thread( new ThreadStart(Decrementer) ),
                 new Thread( new ThreadStart(Incrementer) )
             };

            // start each thread
            int ctr = 1;
            foreach (Thread myThread in myThreads)
            {
                myThread.IsBackground = true;
                myThread.Start(  );
                myThread.Name = "Thread" + ctr.ToString(  );
                ctr++;
                Console.WriteLine("Started thread {0}",
                myThread.Name);
                Thread.Sleep(50);
            }

            // ask the first thread to stop
            myThreads[0].Interrupt(  );

            // tell the second thread to abort immediately
            myThreads[1].Abort(  );

            // wait for all threads to end before continuing
            foreach (Thread myThread in myThreads)
            {
                myThread.Join(  );
            }

            // after all threads end, print a message
            Console.WriteLine("All my threads are done.");
        }

        // demo function, counts down from 100
        public void Decrementer(  )
        {
            try
            {
                for (int i = 100; i >= 0; i−−)
                {
                    Console.WriteLine(
                        "Thread {0}. Decrementer: {1}",
                        Thread.CurrentThread.Name, i);
                    Thread.Sleep(1);
                }
            }
            catch (ThreadAbortException)
            {
                Console.WriteLine(
                    "Thread {0} aborted! Cleaning up...",
                Thread.CurrentThread.Name);
            }
            catch (System.Exception e)
            {
                Console.WriteLine(
                    "Thread has been interrupted ");

           }
            finally
            {
                Console.WriteLine(
                    "Thread {0} Exiting. ",
                    Thread.CurrentThread.Name);
            }
        }

        // demo function, counts up to 100
        public void Incrementer(  )
        {
            try
            {
                for (int i = 0; i < 100; i++)
                {
                    Console.WriteLine(
                        "Thread {0}. Incrementer: {1}",
                        Thread.CurrentThread.Name, i);
                    Thread.Sleep(1);
                }
            }
            catch (ThreadAbortException)
            {
                Console.WriteLine(
                    "Thread {0} aborted!",
                    Thread.CurrentThread.Name);
            }
            catch (System.Exception e)
            {
                Console.WriteLine(
                    "Thread has been interrupted");
            }
            finally
            {
                Console.WriteLine(
                    "Thread {0} Exiting. ",
                    Thread.CurrentThread.Name);
            }
        }
    }
}



Output (excerpt):
Thread Thread2. Incrementer: 42
Thread Thread1. Decrementer: 7
Thread Thread2. Incrementer: 43
Thread Thread1. Decrementer: 6
Thread Thread2. Incrementer: 44
Thread Thread1. Decrementer: 5

Thread Thread2. Incrementer: 45
Thread Thread1. Decrementer: 4
Thread Thread2. Incrementer: 46
Started thread Thread3
Thread Thread3. Decrementer: 100
Thread Thread2. Incrementer: 47
Thread Thread1. Decrementer: 3
Thread Thread2. Incrementer: 48
Thread Thread1. Decrementer: 2
Thread Thread3. Decrementer: 99
Thread Thread2. Incrementer: 49
Thread Thread3. Decrementer: 98
Thread Thread1. Decrementer: 1
Thread Thread1. Decrementer: 0
Thread Thread2. Incrementer: 50
Thread Thread3. Decrementer: 97
Thread Thread2. Incrementer: 51
Thread Thread1 Exiting.
Thread Thread3. Decrementer: 96
...
Thread Thread4. Incrementer: 99
Thread Thread4 Exiting.
All my threads are done.

You see the first thread start and decrement from 100 to 99. The second thread starts, and the two threads are interleaved for a while until the third and fourth threads start. After a short while, however, Thread2 reports that it has been aborted, and then it reports that it is exiting. A little while later, Thread1 reports that it was interrupted. Because the interrupt waits for the thread to be in a wait state, this can be a bit less immediate than a call to Abort. The two remaining threads continue until they are done. They then exit naturally, and the main thread, which was joined on all three, resumes to print its exit message.

Synchronization

At times, you might want to control access to a resource, such as an object’s properties or methods, so that only one thread at a time can modify or use that resource. Your object is similar to the airplane restroom discussed earlier, and the various threads are like the people waiting in line. Synchronization is provided by a lock on the object, which helps the developer avoid having a second thread barge in on your object until the first thread is finished with it.

This section examines three synchronization mechanisms: the Interlock class, the C# lock statement, and the Monitor class. But first, you need to create a shared resource (often a file or printer); in this case, a simple integer variable: counter. You will increment counter from each of two threads.

To start, declare the member variable and initialize it to 0:

int counter = 0;

Modify the Incrementer method to increment the counter member variable:

public void Incrementer(  )
{
 try
 {
    while (counter < 1000)
    {
       int temp = counter;
       temp++; // increment

       // simulate some work in this method
       Thread.Sleep(1);

       // assign the Incremented value
       // to the counter variable
       // and display the results
       counter = temp;
       Console.WriteLine(
       "Thread {0}. Incrementer: {1}",
       Thread.CurrentThread.Name,
      counter);
    }
 }

The idea here is to simulate the work that might be done with a controlled resource. Just as you might open a file, manipulate its contents, and then close it, here, you read the value of counter into a temporary variable, increment the temporary variable, sleep for one millisecond to simulate work, and then assign the incremented value back to counter.

The problem is that your first thread reads the value of counter (0) and assigns that to a temporary variable. Then, it increments the temporary variable. While it is doing its work, the second thread reads the value of counter (still 0), and assigns that value to a temporary variable. The first thread finishes its work, and then assigns the temporary value (1) back to counter and displays it. The second thread does the same. What is printed is 1,1. In the next go around, the same thing happens. Rather than having the two threads count 1,2,3,4, you’ll see 1,2,3,3,4,4. Example 21-3 shows the complete source code and output for this example.

Example 21-3. Simulating a shared resource
using System;
using System.Threading;

namespace SharedResource
{
    class Tester

    {
        private int counter = 0;

        static void Main(  )
        {
            // make an instance of this class
            Tester t = new Tester(  );

            // run outside static Main
            t.DoTest(  );
        }

        public void DoTest(  )
        {
            Thread t1 = new Thread(new ThreadStart(Incrementer));
            t1.IsBackground = true;
            t1.Name = "ThreadOne";
            t1.Start(  );
            Console.WriteLine("Started thread {0}",
            t1.Name);

            Thread t2 = new Thread(new ThreadStart(Incrementer));
            t2.IsBackground = true;
            t2.Name = "ThreadTwo";
            t2.Start(  );
            Console.WriteLine("Started thread {0}",
            t2.Name);
            t1.Join(  );
            t2.Join(  );

            // after all threads end, print a message
            Console.WriteLine("All my threads are done.");
        }

        // demo function, counts up to 1K
        public void Incrementer(  )
        {
            try
            {
                while (counter < 1000)
                {
                    int temp = counter;
                    temp++; // increment

                    // simulate some work in this method
                    Thread.Sleep(1);

                    // assign the decremented value
                    // and display the results
                    counter = temp;
                    Console.WriteLine(

                        "Thread {0}. Incrementer: {1}",
                        Thread.CurrentThread.Name,
                        counter);
                }
            }
            catch (ThreadInterruptedException)
            {
                Console.WriteLine(
                    "Thread {0} interrupted! Cleaning up...",
                    Thread.CurrentThread.Name);
            }
            finally
            {
                Console.WriteLine(
                    "Thread {0} Exiting. ",
                    Thread.CurrentThread.Name);
            }
        }
    }
}

Output:
Started thread ThreadOne
Started thread ThreadTwo
Thread ThreadOne. Incrementer: 1
Thread ThreadOne. Incrementer: 2
Thread ThreadOne. Incrementer: 3
Thread ThreadTwo. Incrementer: 3
Thread ThreadTwo. Incrementer: 4
Thread ThreadOne. Incrementer: 4
Thread ThreadTwo. Incrementer: 5
Thread ThreadOne. Incrementer: 5
Thread ThreadTwo. Incrementer: 6
Thread ThreadOne. Incrementer: 6

Using Interlocked

The CLR provides a number of synchronization mechanisms. These include the common synchronization tools such as critical sections (called locks in .NET), as well as the Monitor class. Each is discussed later in this chapter.

Incrementing and decrementing a value is such a common programming pattern, and one which needs synchronization protection so often that the CLR offers a special class, Interlocked, just for this purpose. Interlocked has two methods, Increment and Decrement, which not only increment or decrement a value, but also do so under synchronization control.

Modify the Incrementer method from Example 21-3 as follows:

public void Incrementer(  )
{
    try
    {
        while (counter < 1000)
        {
            int temp = Interlocked.Increment(ref counter);


            // simulate some work in this method
            Thread.Sleep(0);


            // display the incremented value
            Console.WriteLine(
                "Thread {0}. Incrementer: {1}",
                Thread.CurrentThread.Name, temp);


        }
    }

The catch and finally blocks and the remainder of the program are unchanged from the previous example.

Interlocked.Increment( ) expects a single parameter: a reference to an int. Because int values are passed by value, use the ref keyword, as described in Chapter 4.

Tip

The Increment( ) method is overloaded and can take a reference to a long rather than to an int, if that is what you need.

Once this change is made, access to the counter member is synchronized, and the output is what we’d expect:

Output (excerpts):
Started thread ThreadOne
Started thread ThreadTwo
Thread ThreadOne. Incrementer: 1
Thread ThreadTwo. Incrementer: 2
Thread ThreadOne. Incrementer: 3
Thread ThreadTwo. Incrementer: 4
Thread ThreadOne. Incrementer: 5
Thread ThreadTwo. Incrementer: 6
Thread ThreadOne. Incrementer: 7
Thread ThreadTwo. Incrementer: 8
Thread ThreadOne. Incrementer: 9
Thread ThreadTwo. Incrementer: 10
Thread ThreadOne. Incrementer: 11
Thread ThreadTwo. Incrementer: 12
Thread ThreadOne. Incrementer: 13
Thread ThreadTwo. Incrementer: 14
Thread ThreadOne. Incrementer: 15
Thread ThreadTwo. Incrementer: 16
Thread ThreadOne. Incrementer: 17
Thread ThreadTwo. Incrementer: 18
Thread ThreadOne. Incrementer: 19
Thread ThreadTwo. Incrementer: 20

Using Locks

Although the Interlocked object is fine if you want to increment or decrement a value, there will be times when you want to control access to other objects as well. What is needed is a more general synchronization mechanism. This is provided by the C# lock feature.

A lock marks a critical section of your code, providing synchronization to an object you designate while the lock is in effect. The syntax of using a lock is to request a lock on an object and then to execute a statement or block of statements. The lock is removed at the end of the statement block.

C# provides direct support for locks through the lock keyword. Pass in a reference to an object, and follow the keyword with a statement block:

lock(expression) statement-block

For example, you can modify Incrementer again to use a lock statement, as follows:

public void Incrementer(  )
{
    try
    {
        while (counter < 1000)
        {
            int temp;
            lock (this)
            {
                temp = counter;
                temp++;
                Thread.Sleep(1);
                counter = temp;
            }


            // assign the decremented value
            // and display the results
            Console.WriteLine(
                "Thread {0}. Incrementer: {1}",
                Thread.CurrentThread.Name, temp);


        }
    }

The catch and finally blocks and the remainder of the program are unchanged from the previous example.

The output from this code is identical to that produced using Interlocked.

Using Monitors

The objects used so far will be sufficient for most needs. For the most sophisticated control over resources, you might want to use a monitor. A monitor lets you decide when to enter and exit the synchronization, and it lets you wait for another area of your code to become free.

When you want to begin synchronization, call the Enter( ) method of the monitor, passing in the object you want to lock:

Monitor.Enter(this);

If the monitor is unavailable, the object protected by the monitor is presumed to be in use. You can do other work while you wait for the monitor to become available, and then try again. You can also explicitly choose to Wait( ), suspending your thread until the moment the monitor is free and the developer calls Pulse (discussed in a bit). Wait( ) helps you control thread ordering.

For example, suppose you are downloading and printing an article from the Web. For efficiency, you’d like to print in a background thread, but you want to ensure that at least 10 pages have downloaded before you begin.

Your printing thread will wait until the get-file thread signals that enough of the file has been read. You don’t want to Join the get-file thread because the file might be hundreds of pages. You don’t want to wait until it has completely finished downloading, but you do want to ensure that at least 10 pages have been read before your print thread begins. The Wait( ) method is just the ticket.

To simulate this, rewrite Tester, and add back the decrementer method. Your incrementer counts up to 10. The decrementer method counts down to zero. It turns out you don’t want to start decrementing unless the value of counter is at least 5.

In decrementer, call Enter on the monitor. Then, check the value of counter, and if it is less than 5, call Wait on the monitor:

if (counter < 5)
{
    Monitor.Wait(this);
}

This call to Wait( ) frees the monitor, but signals the CLR that you want the monitor back the next time it is free. Waiting threads are notified of a chance to run again if the active thread calls Pulse( ):

Monitor.Pulse(this);

Pulse( ) signals the CLR that there has been a change in state that might free a thread that is waiting.

When a thread is finished with the monitor, it must mark the end of its controlled area of code with a call to Exit( ):

Monitor.Exit(this);

Example 21-4 continues the simulation, providing synchronized access to a counter variable using a Monitor.

Example 21-4. Using a Monitor object
using System;
using System.Threading;

namespace UsingAMonitor
{
    class Tester
    {
        private long counter = 0;

        static void Main(  )
        {
            // make an instance of this class
            Tester t = new Tester(  );

            // run outside static Main
            t.DoTest(  );
        }

        public void DoTest(  )
        {
            // create an array of unnamed threads
            Thread[] myThreads =
             {
                 new Thread( new ThreadStart(Decrementer) ),
                 new Thread( new ThreadStart(Incrementer) )
             };

            // start each thread
            int ctr = 1;
            foreach (Thread myThread in myThreads)
            {
                myThread.IsBackground = true;
                myThread.Start(  );
                myThread.Name = "Thread" + ctr.ToString(  );
                ctr++;
                Console.WriteLine("Started thread {0}", myThread.Name);
                Thread.Sleep(50);
            }

            // wait for all threads to end before continuing
            foreach (Thread myThread in myThreads)
            {
                myThread.Join(  );
            }

            // after all threads end, print a message
            Console.WriteLine("All my threads are done.");
        }

        void Decrementer(  )
        {
            try
            {
                // synchronize this area of code
                Monitor.Enter(this);

                // if counter is not yet 10
                // then free the monitor to other waiting
                // threads, but wait in line for your turn
                if (counter < 10)
                {
                    Console.WriteLine(
                        "[{0}] In Decrementer. Counter: {1}. Gotta Wait!",
                        Thread.CurrentThread.Name, counter);
                    Monitor.Wait(this);
                }

                while (counter > 0)
                {
                    long temp = counter;
                    temp--;
                    Thread.Sleep(1);
                    counter = temp;
                    Console.WriteLine(
                        "[{0}] In Decrementer. Counter: {1}. ",
                        Thread.CurrentThread.Name, counter);
                }
            }
            finally
            {
                Monitor.Exit(this);
            }
        }

        void Incrementer(  )
        {
            try
            {
                Monitor.Enter(this);
                while (counter < 10)
                {
                    long temp = counter;
                    temp++;
                    Thread.Sleep(1);
                    counter = temp;
                    Console.WriteLine(
                        "[{0}] In Incrementer. Counter: {1}",
                        Thread.CurrentThread.Name, counter);
                }

                // I'm done incrementing for now, let another
                // thread have the Monitor
                Monitor.Pulse(this);
            }
            finally
            {
                Console.WriteLine("[{0}] Exiting...",
                    Thread.CurrentThread.Name);
                Monitor.Exit(this);
            }
        }
    }
}

Output:
Started thread Thread1
[Thread1] In Decrementer. Counter: 0. Gotta Wait!
Started thread Thread2
[Thread2] In Incrementer. Counter: 1
[Thread2] In Incrementer. Counter: 2
[Thread2] In Incrementer. Counter: 3
[Thread2] In Incrementer. Counter: 4
[Thread2] In Incrementer. Counter: 5
[Thread2] In Incrementer. Counter: 6
[Thread2] In Incrementer. Counter: 7
[Thread2] In Incrementer. Counter: 8
[Thread2] In Incrementer. Counter: 9
[Thread2] In Incrementer. Counter: 10
[Thread2] Exiting...
[Thread1] In Decrementer. Counter: 9.
[Thread1] In Decrementer. Counter: 8.
[Thread1] In Decrementer. Counter: 7.
[Thread1] In Decrementer. Counter: 6.
[Thread1] In Decrementer. Counter: 5.
[Thread1] In Decrementer. Counter: 4.
[Thread1] In Decrementer. Counter: 3.
[Thread1] In Decrementer. Counter: 2.
[Thread1] In Decrementer. Counter: 1.
[Thread1] In Decrementer. Counter: 0.
All my threads are done.

In this example, decrementer is started first. In the output, you see Thread1 (the decrementer) start up and then realize that it has to wait. You then see Thread2 start up. Only when Thread2 pulses does Thread1 begin its work.

Try some experiments with this code. First, comment out the call to Pulse( ); you’ll find that Thread1 never resumes. Without Pulse( ), there is no signal to the waiting threads.

As a second experiment, rewrite Incrementer to pulse and exit the monitor after each increment:

void Incrementer(  )
{
    try
    {
        while (counter < 10)
        {
            Monitor.Enter(this);
            long temp = counter;
            temp++;
            Thread.Sleep(1);
            counter = temp;
            Console.WriteLine(
                "[{0}] In Incrementer. Counter: {1}",
                Thread.CurrentThread.Name, counter);
            Monitor.Pulse(this);
            Monitor.Exit(this);
        }
    }
    Catch {}

Rewrite Decrementer as well, changing the if statement to a while statement, and knocking down the value from 10 to 5:

//if (counter < 10)
while (counter < 5)

The net effect of these two changes is to cause Thread2, the Incrementer, to pulse the Decrementer after each increment. While the value is smaller than five, the Decrementer must continue to wait; once the value goes over five, the Decrementer runs to completion. When it is done, the Incrementer thread can run again. The output is shown here:

[Thread2] In Incrementer. Counter: 2
[Thread1] In Decrementer. Counter: 2. Gotta Wait!
[Thread2] In Incrementer. Counter: 3
[Thread1] In Decrementer. Counter: 3. Gotta Wait!
[Thread2] In Incrementer. Counter: 4
[Thread1] In Decrementer. Counter: 4. Gotta Wait!
[Thread2] In Incrementer. Counter: 5
[Thread1] In Decrementer. Counter: 4.
[Thread1] In Decrementer. Counter: 3.
[Thread1] In Decrementer. Counter: 2.
[Thread1] In Decrementer. Counter: 1.
[Thread1] In Decrementer. Counter: 0.
[Thread2] In Incrementer. Counter: 1
[Thread2] In Incrementer. Counter: 2
[Thread2] In Incrementer. Counter: 3
[Thread2] In Incrementer. Counter: 4
[Thread2] In Incrementer. Counter: 5
[Thread2] In Incrementer. Counter: 6
[Thread2] In Incrementer. Counter: 7
[Thread2] In Incrementer. Counter: 8
[Thread2] In Incrementer. Counter: 9
[Thread2] In Incrementer. Counter: 10

Race Conditions and Deadlocks

The .NET library provides sufficient thread support such that you will rarely find yourself creating your own threads or managing synchronization manually.

Thread synchronization can be tricky, especially in complex programs. If you do decide to create your own threads, you must confront and solve all the traditional problems of thread synchronization, such as race conditions and deadlock.

Race Conditions

A race condition exists when the success of your program depends on the uncontrolled order of completion of two independent threads.

Suppose, for example, that you have two threads—one is responsible for opening a file, and the other is responsible for writing to the file. It is important that you control the second thread so that it’s assured that the first thread has opened the file. If not, under some conditions, the first thread will open the file, and the second thread will work fine; under other unpredictable conditions, the first thread won’t finish opening the file before the second thread tries to write to it, and you’ll throw an exception (or worse, your program will simply seize up and die). This is a race condition, and race conditions can be very difficult to debug.

You can’t leave these two threads to operate independently; you must ensure that Thread1 will have completed before Thread2 begins. To accomplish this, you might Join( ) Thread2 on Thread1. As an alternative, you can use a Monitor and Wait( ) for the appropriate conditions before resuming Thread2.

Deadlocks

When you wait for a resource to become free, you are at risk of a deadlock, also called a deadlyn embrace. In a deadlock, two or more threads are waiting for each other, and neither can become free.

Suppose you have two threads, ThreadA and ThreadB. ThreadA locks down an Employee object, and then tries to get a lock on a row in the database. It turns out that ThreadB already has that row locked, so ThreadA waits.

Unfortunately, ThreadB can’t update the row until it locks down the Employee object, which is already locked down by ThreadA. Neither thread can proceed; neither thread will unlock its own resource. They are waiting for each other in a deadly embrace.

As described, a deadlock is fairly easy to spot—and to correct. In a program running many threads, a deadlock can be very difficult to diagnose, let alone solve. One guideline is to get all the locks you need or to release all the locks you have. That is, as soon as ThreadA realizes that it can’t lock the Row, it should release its lock on the Employee object. Similarly, when ThreadB can’t lock the Employee, it should release the Row. A second important guideline is to lock as small a section of code as possible, and to hold the lock as briefly as possible.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.238.226