Implementing a Reader/Writer - Airline Reservation example

Viewed 290

I am not new to C# but I am new to the Thread and Monitor classes. I'm trying to implement a "simple" reader/writer program using "Airline Reservation" as a base. This is a "homework question" taken from the book C# How to Program (Introducing .NET and Web Services) by Deitel.

Here is what I have so far:

This is the class that the Readers and Writers access synchronously. In the interests of brevity and space, I have removed most comments from these classes.

namespace Airline_Reservation
{
    // Represents a single airline reservation
    class Flight
    {
        private string reservation;

        public Flight()
        {
            reservation = "Airline: Unknown\nDate: Unknown\nDeparting Gate: Unknown\n";
        }

        public string Reservation
        {
            get { return reservation; }
            set { reservation = value; }
        }

        public override string ToString() { return Reservation; }

        // This is a required method
        public void StartReading() { Monitor.Enter(this); }

        // This is a required method
        public void StopReading()
        {
            Monitor.Pulse(this);
            Monitor.Exit(this);
        }

        // This is a required method
        public void StartWriting() { Monitor.Enter(this); }

        // This is a required method
        public void StopWriting()
        {
            Monitor.Pulse(this);
            Monitor.Exit(this);
        }
    }
}

Here is the reader:

namespace Airline_Reservation
{
    class Reader
    {
        private string name;
        private static int counter = 0;
        private readonly Flight flightInfo;

        public Reader(Flight info)
        {
            counter += 1;
            name = "Reader " + counter;
            this.flightInfo = info;
        }

        public Reader(Flight info, string name)
        {
            Name = name;
            this.flightInfo = info;
        }

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        // This is a required method
        public void QueryReservation()
        {
            flightInfo.StartReading();
            Console.WriteLine(Name + " reading flight reservation:\n" + flightInfo.ToString());
            flightInfo.StartReading();
        }
    }
}

And here is the writer:

namespace Airline_Reservation
{
    class Writer
    {
        private string name;
        private static int counter = 0;
        private Flight flightInfo;
        private string airlineName;
        private string date;
        private string gate;

        public Writer(Flight flight)
        {
            counter += 1;
            name = "Writer " + counter;
            flightInfo = flight;
        }

        public Writer(Flight flight, string name)
        {
            Name = name;
            flightInfo = flight;
        }

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public string Airline
        {
            get { return airlineName; }
            set { airlineName = value; }
        }

        public string Date
        {
            get { return date; }
            set { date = value; }
        }

        public string Gate
        {
            get { return gate; }
            set { gate = value; }
        }

        // This is a required method
        public void MakeReservation()
        {
            Console.WriteLine(Name + " Making reservation . . .");
            string flightDetails = "Airline: " + Airline + "\nDate: " + Date + "\nGate: " + Gate + "\n";
            flightInfo.StartWriting();
            flightInfo.Reservation = flightDetails;
            flightInfo.StopWriting();
            Console.WriteLine(Name + " Completed reservation.");
            Console.WriteLine(flightDetails);
        }

        // This is a required method
        public void CancelReservation()
        {
            Console.WriteLine(Name + " Cancelling reservation . . .");
            string flightDetails = "Airline: Unknown\nDate: Unknown\nGate:Unknown\n";
            flightInfo.StartWriting();
            flightInfo.Reservation = flightDetails;
            flightInfo.StopWriting();
            Console.WriteLine(Name + " Completed cancellation . . .");
        }
    }
}

I needed a place for the Writer to write into the Flight object so I added three private members to the Writer, adding the values from the main program class.

Here is my main program

namespace Airline_Reservation
{
    class Program
    {
        static void Main(string[] args)
        {
            Flight info = new Flight();

            Reader rdr1 = new Reader(info);
            Reader rdr2 = new Reader(info);
            Reader rdr3 = new Reader(info);
            Reader rdr4 = new Reader(info);
            Reader rdr5 = new Reader(info);
            Writer wtr1 = new Writer(info);

            wtr1.Airline = "Air Canada";
            wtr1.Date = "03/1/2020";
            wtr1.Gate = "J6";

            Thread rdrThread1 = new Thread(new ThreadStart(rdr1.QueryReservation));
            Thread rdrThread2 = new Thread(new ThreadStart(rdr2.QueryReservation));
            Thread wtrThread1 = new Thread(new ThreadStart(wtr1.MakeReservation));
            Thread rdrThread3 = new Thread(new ThreadStart(rdr3.QueryReservation));
            Thread rdrThread4 = new Thread(new ThreadStart(rdr4.QueryReservation));
            Thread wtrThread2 = new Thread(new ThreadStart(wtr1.CancelReservation));
            Thread rdrThread5 = new Thread(new ThreadStart(rdr4.QueryReservation));

            rdrThread1.Priority = ThreadPriority.BelowNormal;
            rdrThread2.Priority = ThreadPriority.Lowest;
            rdrThread3.Priority = ThreadPriority.Normal;
            rdrThread4.Priority = ThreadPriority.BelowNormal;
            rdrThread5.Priority = ThreadPriority.Normal;

            wtrThread1.Priority = ThreadPriority.AboveNormal;
            wtrThread2.Priority = ThreadPriority.AboveNormal;

            rdrThread1.Start();
            rdrThread2.Start();
            rdrThread3.Start();
            rdrThread4.Start();
            rdrThread5.Start();
            wtrThread1.Start();
            wtrThread2.Start();

        }
    }
}

When I run this, I get the following, every time:

Reader 1 reading flight reservation:
Airline: Unknown
Date: Unknown
Departing Gate: Unknown

Writer 1 Cancelling reservation . . .
Writer 1 Making reservation . . .

... and then the program freezes and doesn't proceed. I suspect a deadlock somewhere but since this is my first program explicitly using threads, I don't know where to start.

Any advice or comments?

1 Answers

The first problem I see is:

public void QueryReservation()
{
    flightInfo.StartReading();
    Console.WriteLine(Name + " reading flight reservation:\n" + flightInfo.ToString());
    flightInfo.StartReading();
}

This is going to call Monitor.Enter(this) twice on the same instance and is 100% guaranteed to deadlock. I'm guessing you meant:

public void QueryReservation()
{
    flightInfo.StartReading();
    Console.WriteLine(Name + " reading flight reservation:\n" + flightInfo.ToString());
    flightInfo.StopReading();
}

Monitor is for low-level thread synchronization and you don't seem to be synchronizing anything; you simply overwrite the same Flight instance in every thread. I'm guessing this is just dummy code and you actually want to do some stateful changes. In that case you would be better off with:

public void QueryReservation()
{
    lock(flightInfo)
    {
        Console.WriteLine(Name + " reading flight reservation:\n" + flightInfo.ToString());
        // Do something to flightInfo here.
    }
}

wtrThread1.Priority = ThreadPriority.AboveNormal is not going to ensure the Writer runs before the Reader. That Monitor.Pulse(this) doesn't do anything either; Pulse is to notify another thread to inspect a shared reference and you aren't doing that. If you're trying to implement some kind of thread synchronization, like a Writer passing data to a Reader then waiting until the Reader is finished before passing more data, simply messing around with the Thread priority is not going to do that. Use System.Threading.ReaderWriterLockSlim instead of trying to roll your own low-level synchronization.

Related