Introduction to Transaction Locking

Introduction to Atomicity

Consider the following analogy. In a department store (a store that sells clothes), a customer selects and brings ten (or more) items to the cashier. The cashier is using a cashing machine (also called point of sale or POS) to process the customer's orders but there is also another cashing machine available but nobody is using it at this time. The cashier starts processing the customer's purchase. Another customer comes from somewhere and says that he just has a simple question for the cashier. The cashier stops the processing she was doing in order to answer the other customer's question. After answering the question, the clerk continues serving the first customer. A few seconds later, a manager interrupts the cashier asking why there are so many clothes on the floor throughout the store. The cashier stops what she was doing and thinks about how to answer the manager, but the manager starts talking about something else. To be polite and respectful, the cashier continues listening and seems interested in what the manager is saying. The manager leaves and the clerk resumes what she was doing. Another customer comes from nowhere, asks the waiting customer if he doesn't mind (mind what? Who knows?). The new customer asks the previous customer if he (the new customer) can move ahead because he has only two items and he left his car parked outside in a towing area. The old customer says he doesn't mind. The new customer walks to the cashier who now has to think about helping the new customer while the previous customer is waiting. The clerk things: (*) should she ask the new customer to wait until she has completed the first customer's order? (*) Should she cancel the previous customer order in order to process the new one? (*) Should she use another machine to process the order of the new customer in order to reduce confusion? etc. Let's consider that the cashier serves the new customer and now she must come back to completing the order of the previous customer. What if the previous customer is now upset and starts talking aloud (yelling, cursing, etc). What if the cashier doesn't remember what clothes she had rang already and what others are left? What if the cashier was going to give a discount to the previous customer but now she doesn't remember what type of discount she was going to apply? What if, for some unpredictable reason, the machine has stopped working, in which case the cashier is wondering whether the previous transactions, or some transactions, had been saved, etc.

In the same way, there are many transactions going on inside the computer, and there are many threads running at the same time. The threads must share resources (such as the memory (RAM), the media (hard drives, DVD/Blu-ray players), ports (USB, Wi-Fi, etc). In fact, threads also compete for (the availability of) resources. As a result, threads interrupt each other. Just like in real life where some people drive faster because they think they are more in a hurry than others, some threads want to complete their jobs faster or regardless of what other threads need. Just like in real life, these situations create problems, conflicts, misunderstandings, etc. Both the computer processor and the operating system try to deal with various types of these problems. You too as a programmer can intervene to solve them. One way to address these issues is referred to as atomicity.

Atomicity is the ability to complete a transaction in an all-or-nothing scenario. It means that when a transaction starts, it must be completed. If anything interrupts the transaction before it ends, if anything prevents the transaction from completing, the whole transaction is cancelled as if it never took place. Both the C# language and the .NET Framework provide the means to deal with atomicity.

Locking a Transaction

Locking a transaction consists of isolating its area of work so that nothing else in the flow of the program would be dealt with until that area of work is complete. The use of a lock in C# is done using the lock keyword in a formula as follows:

lock(holder)
{
    // What to do
}

You start with the lock keyword that appears as when calling a method. The job to do, which we referred to as a transaction, is between curly brackets { and }. That code area is called a critical section. Just like in the real world where you must first purchase a lock, in C# you must have a key. You have various options. You can use a string as a key and pass that string as the argument of the lock keyword. Here is an example:

using System.Drawing.Drawing2D;

namespace LockThreading
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();

            Paint += (sender, e) =>
            {
                lock("Eye")
                {
                    Graphics grapher = e.Graphics;
                    Pen penBorder = new Pen(Brushes.Black, 1.855F);

                    RectangleF rectf = new RectangleF(12.225F, 14.308F, 614.147F, 393.792F);
                    LinearGradientBrush lgb = new LinearGradientBrush(rectf,
                                                                      Color.Maroon,
                                                                      Color.PeachPuff,
                                                                      LinearGradientMode.Horizontal);

                    PointF[] ptsFace  = { new( 22.428F, 112.488F),
                                          new(624.974F, 112.488F),
                                          new(624.974F, 404.716F),
                                          new( 22.428F, 404.716F) };
                    PointF[] ptsTop   = { new( 22.428F, 112.488F),
                                          new(154.340F,  22.116F),
                                          new(764.886F,  22.116F),
                                          new(624.974F, 112.488F) };
                    PointF[] ptsRight = { new(624.974F, 112.488F),
                                          new(764.886F,  22.116F),
                                          new(764.886F, 322.664F),
                                          new(624.974F, 404.716F) };

                    grapher.FillPolygon(lgb, ptsFace);
                    grapher.DrawPolygon(penBorder, ptsFace);

                    rectf = new(12.225F, 14.308F, 605.554F, 98.402F);
                    lgb = new LinearGradientBrush(rectf,
                                                 Color.DarkRed,
                                                 Color.DarkOrange,
                                                 LinearGradientMode.Vertical);

                    grapher.FillPolygon(lgb, ptsTop);
                    grapher.DrawPolygon(penBorder, ptsTop);

                    rectf = new(620.225F, 21.308F, 145.55F, 302.418F);
                    lgb = new LinearGradientBrush(rectf,
                                                 Color.DarkOrange,
                                                 Color.DarkRed,
                                                 LinearGradientMode.Horizontal);
                    grapher.FillPolygon(lgb, ptsRight);
                    grapher.DrawPolygon(penBorder, ptsRight);
                }
            };
        }
    }
}

Introduction to Atomicity

Actually, the key must be a reference type. This means that you must use an object that is of the object type or a class (not a structure type, and remember that any class you create in C# is directly or indirectly derived from the Object class). To provide this object, you can first declare a variable of the desired type. If you declare the variable, you must initialize it using any constructor of that class. The name of the variable follows the rules of names of variables in C#. You will pass that variable to lock(). Here is an example:

namespace LockThreading1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();

            object locker = new object();

            Paint += (sender, e) =>
            {
                lock (locker)
                {
                    Point pt1 = new Point(50, 62);
                    Point pt2 = new Point(88, 346);
                    Point pt3 = new Point(785, 392);
                    Point pt4 = new Point(650, 118);
                    Point[] pts = { pt1, pt2, pt3, pt4 };

                    e.Graphics.DrawEllipse(new(Color.Red, 2.215F), new Rectangle(pt1.X - 15, pt1.Y - 15, 30, 30));
                    e.Graphics.DrawEllipse(new(Color.Red, 2.215F), new Rectangle(pt2.X - 15, pt2.Y - 15, 30, 30));
                    e.Graphics.DrawEllipse(new(Color.Red, 2.215F), new Rectangle(pt3.X - 15, pt3.Y - 15, 30, 30));
                    e.Graphics.DrawEllipse(new(Color.Red, 2.215F), new Rectangle(pt4.X - 15, pt4.Y - 15, 30, 30));

                    e.Graphics.DrawCurve(new Pen(Color.Blue, 5.228F), pts);
                }
            };
        }
    }
}

Introduction to Atomicity

In most cases, you will not use the key other than passing it to lock(). Based on this, you don't have to first declare a variable. You can pass the new keyword and the constructor in the parentheses of lock(). Here is an example:

namespace LockThreading1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();

            Paint += (sender, e) =>
            {
                lock(new object())
                {
                    Graphics grapher = e.Graphics;
                    
                    PointF   pt1 = new PointF( 44.227F,  42.639F);
                    PointF   pt2 = new PointF(288.353F, 356.552F);
                    PointF   pt3 = new PointF(754.602F, 192.816F);
                    PointF   pt4 = new PointF(352.882F,  48.679F);
                    PointF[] pts = { pt1, pt2, pt3, pt4 };
                    
                    grapher.FillRectangle(Brushes.Red, new RectangleF(pt1.X - 15, pt1.Y - 15, 30, 30));
                    grapher.FillRectangle(Brushes.Red, new RectangleF(pt2.X - 15, pt2.Y - 15, 30, 30));
                    grapher.FillRectangle(Brushes.Red, new RectangleF(pt3.X - 15, pt3.Y - 15, 30, 30));
                    grapher.FillRectangle(Brushes.Red, new RectangleF(pt4.X - 15, pt4.Y - 15, 30, 30));
                    
                    Pen penCurrent = new Pen(Color.DarkGreen, 5.618F);
                    grapher.DrawCurve(penCurrent, pts, 2.235F);
                }
            };
        }
    }
}

Introduction to Atomicity

When you use a lock in C#, no thread should (or can) interrupt the transaction(s) of that lock; that is, everything in its curly brackets must complete. Any thread that reaches that section must wait.

In the real world, you can use only one lock per item such as per house or suitcase, etc. In your application, you can use the same lock in different critical sections (such as in different methods or inside the same method). This is said that the sections (or methods) share the same lock. Here is an example:

using System.Drawing.Drawing2D;

namespace LockThreading1
{
    public partial class Exercise : Form
    {
        Point[] ptStars = new Point[100];

        public Exercise()
        {
            InitializeComponent();
        }

        private void Exercise_Load(object sender, EventArgs e)
        {
            Random rndPoint = new Random();

            for (int i = 0; i < 100; i++)
            {
                ptStars[i] = new Point(rndPoint.Next(0, 1000), rndPoint.Next(0, 1000));
            }
        }

        private void Exercise_Paint(object sender, PaintEventArgs e)
        {
            Graphics grapher = e.Graphics;

            int    radius        = 80;
            Form   closedCircuit = new Form();
            Random rndPoint      = new Random();
            Random rndStarSize   = new Random();

            Point     ptCenter   = new Point(ClientRectangle.Width / 2, ClientRectangle.Height / 2);
            Rectangle rectSun    = new Rectangle(ptCenter.X - radius - 5, ptCenter.Y - radius - 5, (radius * 2) + 10, (radius * 2) + 10);

            Rectangle rSun = rectSun;
            
            rSun.Inflate(-10, -10);
            
            LinearGradientBrush lgbSun = new LinearGradientBrush(rSun, Color.Maroon, Color.Yellow, LinearGradientMode.ForwardDiagonal);

            grapher.Clear(Color.Black);


            for (int i = 0; i < 100; i++)
            {
                grapher.FillRectangle(Brushes.White, new Rectangle(ptStars[i].X - 1, ptStars[i].Y - 1, 2, 2));
            }

            lock (closedCircuit)
            {
                for (int i = 0; i < 100; i++)
                {
                    ptStars[i] = new Point(rndPoint.Next(0, ClientRectangle.Width), rndPoint.Next(0, ClientRectangle.Height));
                    Point pt1  = new Point(ptStars[i].X - rndStarSize.Next(1, 3), ptStars[i].Y);
                    Point pt2  = new Point(ptStars[i].X, ptStars[i].Y - rndStarSize.Next(2, 6));
                    Point pt3  = new Point(ptStars[i].X + rndStarSize.Next(1, 3), ptStars[i].Y);
                    Point pt4  = new Point(ptStars[i].X, ptStars[i].Y + rndStarSize.Next(2, 6));
                    Point[] ptStarPolygon = { pt1, pt2, pt3, pt4 };

                    grapher.FillPolygon(Brushes.White, ptStarPolygon);
                }
            }

            lock (closedCircuit)
            {

                grapher.FillEllipse(lgbSun, rSun);
                grapher.DrawEllipse(Pens.White, new Rectangle(ptCenter.X - radius -  25, ptCenter.Y - radius -  25, (radius * 2) +  50, (radius * 2) +  50));
                grapher.DrawEllipse(Pens.White, new Rectangle(ptCenter.X - radius -  75, ptCenter.Y - radius -  75, (radius * 2) + 150, (radius * 2) + 150));
                grapher.DrawEllipse(Pens.White, new Rectangle(ptCenter.X - radius - 125, ptCenter.Y - radius - 125, (radius * 2) + 250, (radius * 2) + 250));
                grapher.DrawEllipse(Pens.White, new Rectangle(ptCenter.X - radius - 175, ptCenter.Y - radius - 175, (radius * 2) + 350, (radius * 2) + 350));
                grapher.DrawEllipse(Pens.White, new Rectangle(ptCenter.X - radius - 225, ptCenter.Y - radius - 225, (radius * 2) + 450, (radius * 2) + 450));
                grapher.DrawEllipse(Pens.White, new Rectangle(ptCenter.X - radius - 275, ptCenter.Y - radius - 275, (radius * 2) + 550, (radius * 2) + 550));
                grapher.DrawEllipse(Pens.White, new Rectangle(ptCenter.X - radius - 325, ptCenter.Y - radius - 325, (radius * 2) + 650, (radius * 2) + 650));
            }
        }

        private void tmrStars_Tick(object sender, EventArgs e)
        {
            Invalidate();
        }
    }
}

Introduction to Atomicity

Introduction to Atomicity

Normally, you should avoid sharing a lock. If you need to use many critical sections in your code, declare a variable for each. The lock variables can be of the same type or different types, as long as their names are different. Here is an example:

using System.Drawing.Drawing2D;

namespace LockThreading1
{
    public partial class Exercise : Form
    {
        King heir = new King(24);
        House protection = new House();
        Form closedCircuit = new Form();

        Point[] ptStars = new Point[100];

        public Exercise()
        {
            InitializeComponent();
        }

        private void Exercise_Load(object sender, EventArgs e)
        {
            Random rndPoint = new Random();

            for (int i = 0; i < 100; i++)
            {
                ptStars[i] = new Point(rndPoint.Next(0, 1000), rndPoint.Next(0, 1000));
            }
        }

        private void Exercise_Paint(object sender, PaintEventArgs e)
        {
            Graphics grapher = e.Graphics;

            int radius = 80;
            //Random rndStars = new Random();
            Random rndPoint = new Random();
            Random rndStarSize = new Random();

            //Point[] ptStarPolygons = new Point[100];
            Point ptCenter = new Point(ClientRectangle.Width / 2, ClientRectangle.Height / 2);
            Rectangle rectSun = new Rectangle(ptCenter.X - radius - 5, ptCenter.Y - radius - 5, (radius * 2) + 10, (radius * 2) + 10);
            Rectangle rSun = rectSun;
            rSun.Inflate(-10, -10);
            LinearGradientBrush lgbSun = new LinearGradientBrush(rSun, Color.Maroon, Color.Yellow, LinearGradientMode.ForwardDiagonal);

            e.Graphics.Clear(Color.Black);


            for (int i = 0; i < 100; i++)
            {
                grapher.FillRectangle(Brushes.White, new Rectangle(ptStars[i].X - 1, ptStars[i].Y - 1, 2, 2));
            }
                

            lock (closedCircuit)
            {
                for (int i = 0; i < 100; i++)
                {
                    ptStars[i] = new Point(rndPoint.Next(0, ClientRectangle.Width), rndPoint.Next(0, ClientRectangle.Height));
                    Point pt1  = new Point(ptStars[i].X - rndStarSize.Next(1, 3), ptStars[i].Y);
                    Point pt2  = new Point(ptStars[i].X, ptStars[i].Y - rndStarSize.Next(2, 6));
                    Point pt3  = new Point(ptStars[i].X + rndStarSize.Next(1, 3), ptStars[i].Y);
                    Point pt4  = new Point(ptStars[i].X, ptStars[i].Y + rndStarSize.Next(2, 6));
                    Point[] ptStarPolygon = { pt1, pt2, pt3, pt4 };

                    grapher.FillPolygon(Brushes.White, ptStarPolygon);
                }
            }

            lock (heir)
            {

                grapher.FillEllipse(lgbSun, rSun);
                grapher.DrawEllipse(Pens.White, new Rectangle(ptCenter.X - radius - 25, ptCenter.Y - radius - 25, (radius * 2) + 50, (radius * 2) + 50));
                grapher.DrawEllipse(Pens.White, new Rectangle(ptCenter.X - radius - 75, ptCenter.Y - radius - 75, (radius * 2) + 150, (radius * 2) + 150));
                grapher.DrawEllipse(Pens.White, new Rectangle(ptCenter.X - radius - 125, ptCenter.Y - radius - 125, (radius * 2) + 250, (radius * 2) + 250));
                grapher.DrawEllipse(Pens.White, new Rectangle(ptCenter.X - radius - 175, ptCenter.Y - radius - 175, (radius * 2) + 350, (radius * 2) + 350));
                grapher.DrawEllipse(Pens.White, new Rectangle(ptCenter.X - radius - 225, ptCenter.Y - radius - 225, (radius * 2) + 450, (radius * 2) + 450));
                grapher.DrawEllipse(Pens.White, new Rectangle(ptCenter.X - radius - 275, ptCenter.Y - radius - 275, (radius * 2) + 550, (radius * 2) + 550));
                grapher.DrawEllipse(Pens.White, new Rectangle(ptCenter.X - radius - 325, ptCenter.Y - radius - 325, (radius * 2) + 650, (radius * 2) + 650));
            }

            lock (protection)
            {
                grapher.FillEllipse(Brushes.Orange, new Rectangle((ClientRectangle.Width / 2) - 100, (ClientRectangle.Height / 2) + 45, 32, 32));
                grapher.FillEllipse(Brushes.DarkOrchid, new Rectangle((ClientRectangle.Width / 2) + 120, (ClientRectangle.Height / 2) - 70, 45, 45));
                grapher.FillEllipse(Brushes.Blue, new Rectangle((ClientRectangle.Width / 2) + 130, (ClientRectangle.Height / 2) + 110, 50, 50));
                grapher.FillEllipse(Brushes.Maroon, new Rectangle((ClientRectangle.Width / 2) - 80, (ClientRectangle.Height / 2) - 280, 75, 75));
                grapher.FillEllipse(Brushes.DarkOrange, new Rectangle((ClientRectangle.Width / 2) - 300, (ClientRectangle.Height / 2) + 120, 65, 65));
                grapher.FillEllipse(Brushes.Wheat, new Rectangle((ClientRectangle.Width / 2) + 220, (ClientRectangle.Height / 2) - 200, 70, 70));
                grapher.FillEllipse(Brushes.DarkKhaki, new Rectangle((ClientRectangle.Width / 2) - 350, (ClientRectangle.Height / 2) - 180, 60, 60));
                grapher.FillEllipse(Brushes.LawnGreen, new Rectangle((ClientRectangle.Width / 2) + 180, (ClientRectangle.Height / 2) + 335, 35, 35));
            }
        }

        private void tmrStars_Tick(object sender, EventArgs e)
        {
            Invalidate();
        }
    }

    record House { }

    record King
    {
        public King(int level)
        {
        }
    }
}

Introduction to Atomicity

Introduction to Atomicity

In the same way, avoid using the same string as the object (name) of many locks.

Nesting a Lock

You can create a critical section inside another critical section. Each section must use its own lock. The nested critical section can appear anywhere inside the nesting section, such as before some of its code lines, after some code lines but before other code lines, or just before the closing curly bracket. Here is an example:

namespace LockThreading2
{
    public partial class Exercise : Form
    {
        object bordering = new object();
        object filling = new object();

        public Exercise()
        {
            InitializeComponent();

            Paint += (sender, e) =>
            {
                lock (bordering)
                {
                    Graphics grapher = e.Graphics;

                    PointF[] ptsLeft  = { new( 22.225F, 452.225F),
                                          new( 22.225F, 102.225F),
                                          new(202.225F, 202.225F),
                                          new(202.225F, 552.225F) };
                    PointF[] ptsTop   = { new( 22.225F, 102.225F),
                                          new(202.225F,  22.225F),
                                          new(552.225F,  22.225F),
                                          new(752.225F, 102.225F),
                                          new(552.225F, 202.225F),
                                          new(202.225F, 202.225F) };
                    PointF[] ptsRight = { new(552.225F, 552.225F),
                                          new(552.225F, 202.225F),
                                          new(752.225F, 102.225F),
                                          new(752.225F, 452.225F) };
                    PointF[] ptsFace  = { new(202.225F, 552.225F),
                                          new(202.225F, 202.225F),
                                          new(552.225F, 202.225F),
                                          new(552.225F, 552.225F) };
                    
                    grapher.FillPolygon(Brushes.LightSkyBlue, ptsLeft);
                    grapher.FillPolygon(Brushes.DodgerBlue, ptsTop);
                    grapher.FillPolygon(Brushes.LightSkyBlue, ptsRight);
                    grapher.FillPolygon(Brushes.Blue, ptsFace);

                    lock (filling)
                    {
                        Pen pnBorder = new Pen(Brushes.Black, 5.625F);

                        grapher.DrawLine(pnBorder, ptsTop[0].X,   ptsTop[0].Y,   ptsTop[1].X,   ptsTop[1].Y);
                        grapher.DrawLine(pnBorder, ptsTop[1].X,   ptsTop[1].Y,   ptsTop[2].X,   ptsTop[2].Y);
                        grapher.DrawLine(pnBorder, ptsTop[2].X,   ptsTop[2].Y,   ptsTop[3].X,   ptsTop[3].Y);
                        grapher.DrawLine(pnBorder, ptsTop[3].X,   ptsTop[3].Y,   ptsTop[4].X,   ptsTop[4].Y);
                        grapher.DrawLine(pnBorder, ptsTop[4].X,   ptsTop[4].Y,   ptsTop[5].X,   ptsTop[5].Y);
                        grapher.DrawLine(pnBorder, ptsTop[5].X,   ptsTop[5].Y,   ptsTop[0].X,   ptsTop[0].Y);
                        grapher.DrawLine(pnBorder, ptsLeft[0].X,  ptsLeft[0].Y,  ptsLeft[1].X,  ptsLeft[1].Y);
                        grapher.DrawLine(pnBorder, ptsFace[0].X,  ptsFace[0].Y,  ptsFace[1].X,  ptsFace[1].Y);
                        grapher.DrawLine(pnBorder, ptsFace[3].X,  ptsFace[3].Y,  ptsFace[2].X,  ptsFace[2].Y);
                        grapher.DrawLine(pnBorder, ptsRight[2].X, ptsRight[2].Y, ptsRight[3].X, ptsRight[3].Y);
                        grapher.DrawLine(pnBorder, ptsLeft[0].X,  ptsLeft[0].Y,  ptsLeft[3].X,  ptsLeft[3].Y);
                        grapher.DrawLine(pnBorder, ptsFace[0].X,  ptsFace[0].Y,  ptsFace[3].X,  ptsFace[3].Y);
                        grapher.DrawLine(pnBorder, ptsRight[0].X, ptsRight[0].Y, ptsRight[3].X, ptsRight[3].Y);
                    }
                }
            };
        }
    }
}}

Nesting a Lock

In the same way, you can nest many critical sections in one, or nest a critical section in one that itself is nested.

Monitoring Threads

Introduction

While the C# language provides the lock keyword to isolate a transaction that must complete without interruption, the .NET Framework supports the locking concept through a static class named Monitor.

As we will learn in later sections, the Monitor class goes beyond the limitations of the lock keyword. For example, the C#'s lock is mostly used to create a section of code for atomicity. On the other hand, the Monitor class goes behind-the-scenes to manage the lock used by the current object such as to restrict its access and/or to release it. To be able to manage the various locks in an application, Monitor is a static class with various methods that, of course, are all static. This means that you will not declare a variable for this class. Instead, you can just call the method you need (provided you know what the method does).

Entering a Critical Section

To let you start delimiting a critical section, the Monitor class is equipped with an overloaded method named Enter that has two versions. One of them uses the following syntax:

public static void Enter(object obj)

As seen for the lock keyword, the Monitor.Enter() method takes an argument that must be a reference type. Here is an example:

namespace MonitoringThreads
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();

            Monitor.Enter(new object());
        }
    }
}

The other version of the Monitor.Enter() method uses the following syntax:

public static void Enter(object obj, ref bool lockTaken)

Besides the key, this version takes a Boolean argument as reference. This argument is the value returned by the method to indicate whether the lock was actually acquired (if it returns true) or not (if it returns false).

Exiting a Critical Section

To let you indicate the end of a critical section, the Monitor class is equipped with a method named Exit. Its syntax is:

public static void Exit(object obj)

This method takes an argument as the (same) object that was passed to the Monitor.Enter() method. For this reason, you should (must) explicitly declare a variable and pass it as argument to both methods. Here is an example:

Exiting a Critical Section

namespace MonitoringThreads
{
    public partial class Exercise : Form
    {
        private int x;
        private bool isMovingRight;

        public Exercise()
        {
            InitializeComponent();
        }

        private void Exercise_Load(object sender, EventArgs e)
        {
            x = 0;
            isMovingRight = false;

            BackColor = Color.Black;
        }

        private void pbxCanvas_Paint(object sender, PaintEventArgs e)
        {
            object line = new object();

            Monitor.Enter(line);

            // If the status of the origin indicates that it is moving right,
            // then increase the horizontal axis
            if (isMovingRight == true)
                x++;

            else // If it's moving left, then decrease the horizontal movement
                x--;

            /* Collision: if the axis hits the right side of the screen,
             then set the horizontal moving status to "Right", which will be used
             by the above code */
            if ((x + 40) > ClientSize.Width)
                isMovingRight = false;

            if (x < 0)
                isMovingRight = true;

            // Draw the vertical axis
            e.Graphics.DrawLine(Pens.Aqua, x + 20, 0, x + 20, ClientSize.Height);

            Monitor.Exit(line);
        }

        private void tmrControlCircles_Tick(object sender, EventArgs e)
        {
            pbxCanvas.Invalidate();
        }
    }
}

This would produce:

Exiting a Critical Section

Exiting a Critical Section

After using a lock, to indicate to the other objects that the lock has been released, you should create a try...finally block in your code. In this case, call the Monitor.Exit() method in the finally section. This can be done as follows:

namespace MonitoringThreads
{
    public partial class Exercise : Form
    {
        private int x;
        private bool isMovingRight;

        public Exercise()
        {
            InitializeComponent();
        }

        private void Exercise_Load(object sender, EventArgs e)
        {
            x = 0;
            isMovingRight = false;

            BackColor = Color.Black;
        }

        private void pbxCanvas_Paint(object sender, PaintEventArgs e)
        {
            object line = new object();

            Monitor.Enter(line);

            try
            {
                if (isMovingRight == true)
                    x++;

                else
                    x--;

                if ((x + 40) > ClientSize.Width)
                    isMovingRight = false;

                if (x < 0)
                    isMovingRight = true;

                e.Graphics.DrawLine(Pens.Aqua, x + 20, 0, x + 20, ClientSize.Height);
            }
            finally
            {
                Monitor.Exit(line);
            }
        }

        private void tmrControlCircles_Tick(object sender, EventArgs e)
        {
            pbxCanvas.Invalidate();
        }
    }
}

In the same way, you can create as many monitors as you want as long as each call to Monitor.Enter() and Monitor.Exit() use the same object. Here are examples:

namespace MonitoringThreads
{
    public partial class Exercise : Form
    {
        private int x;
        private int y;
        private bool isMovingRight;
        private bool isMovingDown;

        public Exercise()
        {
            InitializeComponent();
        }

        private void Exercise_Load(object sender, EventArgs e)
        {
            x = 0;
            y = 0;
            isMovingRight = false;
            isMovingDown = false;

            BackColor = Color.Black;
        }

        private void pbxCanvas_Paint(object sender, PaintEventArgs e)
        {
            object vertical = new object();
            object horizontal = new object();

            Monitor.Enter(vertical);

            try
            {
                // If the status of the origin indicates that it is moving right,
                // then increase the horizontal axis
                if (isMovingRight == true)
                    x++;

                else // If it's moving left, then decrease the horizontal movement
                    x--;

                /* Collision: if the axis hits the right side of the screen,
                 then set the horizontal moving status to "Right", which will be used
                 by the above code */
                if ((x + 40) > ClientSize.Width)
                    isMovingRight = false;

                if (x < 0)
                    isMovingRight = true;

                // Draw the vertical axis
                e.Graphics.DrawLine(Pens.Aqua, x + 20, 0, x + 20, ClientSize.Height);
            }
            finally
            {
                Monitor.Exit(vertical);
            }

            Monitor.Enter(horizontal);

            try
            {
                // If the status of the origin indicates that it is moving down,
                // then increase the vertical axis
                if (isMovingDown == true)
                    y++;
                else // Otherwise, decrease it
                    y--;

                if ((y + 40) > ClientSize.Height)
                    isMovingDown = false;

                if (y < 0)
                    isMovingDown = true;

                // Draw the vertical axis
                e.Graphics.DrawLine(Pens.Aqua, 0, y + 20, ClientSize.Width, y + 20);
            }
            finally
            {
                Monitor.Exit(horizontal);
            }
        }

        private void tmrControlCircles_Tick(object sender, EventArgs e)
        {
            pbxCanvas.Invalidate();
        }
    }
}

This would produce:

Exiting a Critical Section

Exiting a Critical Section

As we saw for locks, you can nest a monitored section inside another.


Previous Copyright © 2014-2024, FunctionX Tuesday 02 July 2024, 10:55 Next