Wait and Pulse Method in C# Threading


Introduction

The monitor class have two static method Wait() and Pulse(). The purpose of Wait and Pulse is to provide a simple signaling mechanism: Wait blocks until it receives notification from another thread; Pulse provides that notification.

Wait must execute before Pulse in order for the signal to work. If Pulse executes first, its pulse is lost, and the late waiter must wait for a fresh pulse, or remain forever blocked. This differs from the behavior of an AutoResetEvent, where its Set method has a "latching" effect and so is effective if called before WaitOne.

One must specify a synchronizing object when calling Wait or Pulse. If two threads use the same object, then they are able to signal each other. The synchronizing object must be locked prior to calling Wait or Pulse.

When a thread is temporarily blocked from running, it calls Wait() This causes the thread to go to sleep and the lock for that object to be released, allowing another thread to use the object. At a later point, the sleeping thread is awakened when some other thread enters the same lock and calls Pulse() or PulseAll(). A call to Pulse() resumes the first thread in the queue of threads waiting for the lock. A call to PulseAll signals the release of the lock to all waiting threads.

Here are two commonly used forms of Wait():

public static bool Wait(object waitObject)
public static bool Wait(object waitObject, int milliseconds)

The first form waits until notified. The second form waits until notified or until the specified period of milliseconds has expired. For both waitObject specifies the object upon which to wait.

Here are the general forms for Pulse() and PulseAll():

public static void Pulse(object waitObject)
public static void PulseAll(object waitObject)

Here, waitObject is the object being released.

To understand the need for and the application of Wait() and Pulse() we create following program

using System;
using System.Threading;
namespace WaitndPulesMethod
{
class PingPong
    {
        public void ping(bool running)
        {
            lock (this)
            {
                if (!running)
                {
                    //ball halts.
                    Monitor.Pulse(this); // notify any waiting threads
                    return;
                }
                Console.Write("Ping ");
                Monitor.Pulse(this); // let pong() run
                Monitor.Wait(this); // wait for pong() to complete
            }
        }
        public void pong(bool running)
        {
            lock (this)
            {
                if (!running)
                {
                    //ball halts.
                    Monitor.Pulse(this); // notify any waiting threads
                    return;
                }
                Console.WriteLine("Pong ");
                Monitor.Pulse(this); // let ping() run
                Monitor.Wait(this); // wait for ping() to complete
            }
        }
    }
    class MyThread
    {
        public Thread thread;
        PingPong pingpongObject;
        //construct a new thread.
        public MyThread(string name, PingPong pp)
        {
            thread = new Thread(new ThreadStart(this.run));
            pingpongObject = pp;
            thread.Name = name;
            thread.Start();
        }
        //Begin execution of new thread.
        void run()
        {
            if (thread.Name == "Ping")
            {
                for (int i = 0; i < 5; i++)
                    pingpongObject.ping(true);
                pingpongObject.ping(false);
            }
            else
            {
                for (int i = 0; i < 5; i++)
                    pingpongObject.pong(true);
                pingpongObject.pong(false);
            }
        }
    }
    class BouncingBall
    {
        public static void Main()
        {
            PingPong pp = new PingPong();
            Console.WriteLine("The Ball is dropped... \n");
            MyThread mythread1 = new MyThread("Ping", pp);
            MyThread mythread2 = new MyThread("Pong", pp); 
            mythread1.thread.Join();
            mythread2.thread.Join();
            Console.WriteLine("\nThe Ball Stops Bouncing.");
            Console.Read();
        }
    }
}

Output

 wait.jpg

The principle is that you write the signaling logic yourself using custom flags and fields (in conjunction with lock statements), then introduce Wait and Pulse commands to mitigate CPU spinning. This advantage of this low-level approach is that with just Wait, Pulse and the lock statement, you can achieve the functionality of AutoResetEvent, ManualResetEvent and Semaphore, as well as WaitHandle's static methods WaitAll() and WaitAny(). Furthermore,Wait and Pulse can be amenable in situations where all of the Wait Handles are parsimoniously challenged.

A SynchronizationLockException will be thrown if Wait(), Pulse or PulseAll() is called from code that is not within a lock block.