Primary Characteristics of a Thread

The Name of a Thread

When you create a thread, to further identify it in your code, you can/should give it a name. If you don't specify a name, the compiler automatically gives it a null name. To let you specify the name of your thread, the Thread class provides the Name property:

public string Name { get; set; }

This property allows you to not only name a thread but also to get the name of a thread. Here is an example of using this property (the form is designed as done in the previous lesson):

using System.Threading;

namespace RoadTrafficMonitoring
{
    public partial class TrafficMonitoring : Form
    {
        Thread thVehicle;
        int xPosition;
        int yPosition;
        
        public TrafficMonitoring()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Random rndNumber = new Random();

            xPosition = rndNumber.Next(0, 1024);
            yPosition = 538;
        }

        void MoveVehicle()
        {
            if (xPosition < pbxRoadMap.Width)
                xPosition++;
            else
                xPosition = -100;

            Text = thVehicle.Name;
        }

        private void pbxRoadMap_Paint(object sender, PaintEventArgs e)
        {
            pbxLRVehicle.Location = new Point(xPosition, yPosition);
        }

        private void tmrTrafficMonitoring_Tick(object sender, EventArgs e)
        {
            ThreadStart tsVehicle = new ThreadStart(MoveVehicle);
            thVehicle = new Thread(tsVehicle);

            thVehicle.Name = "Traffic Monitoring";

            thVehicle.Start();

            pbxRoadMap.Invalidate();
        }
    }
}

The Identifier of a Managed Thread

As threads are running in the computer, the operating system assigns a unique number to each. To let you find out what that number is, the Thread class is equipped with a read-only property named ManagedThreadId:

public int ManagedThreadId { get; }

The Thread Has Started, Or Not

As mentioned already, when you create a thread, you are simply indicating to the operating system that you will need a thread. To actually use the thread, you must start it. This changes the execution of the thread to being alive. On the other hand, after using a thread, you can terminate it by calling the Abort() method. This action also changes the status of the thread to being dead. At any time, to let you find out whether a thread is alive or not, the Thread class is equipped with a read-only Boolean property named IsAlive:

public bool IsAlive { get; }

If the thread that accesses this property started already, this property holds a true value. If the thread was terminated already, this property holds a false value.

Scheduling Threads

On one hand, a process (or application) uses many threads and some threads are running at the same time. Also, many processes are used in the computer at the same time. On the other hand, a computer has limited resources. For example in the past, most computers had only one processor. Even though nowadays processors are becoming more and more sophisticated, computers also now have to honor many requests at the same time, for limited resources. For example, most computers have only one motherboard, one sound system, one DVD or Blu-ray player if any, one audio port, one network port (RJ-45 or Wi-Fi), etc.

To address the infinite requests it receives, the operating system uses an internal program (or algorithm) named a task scheduler, or just a scheduler. To do this, the scheduler keeps a first-in-first-out list, like a queue where people stand at the bank, a supermarket, or the airport in front of a cashier, ready to be served. By default, the first item in the queue (the first person arrived) is also the first to be served. Of course, items (people) keep added (or coming) to the queue and those that (who) have been served leave.

As life experience has proved so many times, some people are in a hurry more than others or for some reasons, some people should (must) be served faster than others. For example, important people such as politicians and movie stars must be served first. Such situations also apply to threads. For example, while a media player of a machine or device (desktop computer, laptop, tablet PC, cell phone, etc) is playing music through the machine's sound system, when some applications such as email messengers (Hotmail, Yahoo, Gmail, etc) receive an update or notification, they may have to (sometimes must) immediately let the user know, which is done by playing a sound bite through the same speakers the media player is using. As a result, some threads must go ahead and/or bypass what other threads are doing. Normally, this is usually a temporary or very fast situation and the user doesn't experience any real interruption.

The Importance/Priority of a Thread

Not all threads are created equal, and threads must share the resources of the computer. As a result, some threads must be executed before other threads. In most cases, the OS is in charge (and knows how (it is equipped) to take care) of deciding what thread must execute before what thread. Still, in a custom application, you can decide what thread has a more important job. To let you set this aspect, the Thread class is equipped with a property named Priority:

public ThreadPriority Priority { get; set; }

The value of the Thread.Priority property comes from an enumeration named ThreadPriority. The members of this enumeration are (the names should be self-explanatory): Highest, AboveNormal, Normal, BelowNormal, and Lowest. Here is an example of using this property (this is not the best example but it was the only thing I could come up with; this example doesn't really illustrate thread prioritization):

using System.Threading;

public class Exercise : Form
{
    private System.Windows.Forms.PictureBox pbxRoadMap;
    private System.Windows.Forms.Timer tmrDrawVehicles;

    private float LeftRightSpeed1, LeftRightSpeed2;
    private float xLeftRightVehicle1, yLeftRightVehicle1;
    private float xLeftRightVehicle2, yLeftRightVehicle2;

    private Image imgBackground;
    private Image imgLRVehicle1, imgLRVehicle2;

    private Thread thLeftRightVehicle1 = null;
    private Thread thLeftRightVehicle2 = null;

    public Exercise()
    {
        InitializeComponent();
    }

    private void InitializeComponent()
    {
        pbxRoadMap = new System.Windows.Forms.PictureBox();
        pbxRoadMap.Dock = DockStyle.Fill;
        pbxRoadMap.Paint += new PaintEventHandler(pbxRoadMapPaint);
        Controls.Add(pbxRoadMap);

        tmrDrawVehicles = new System.Windows.Forms.Timer();
        tmrDrawVehicles.Interval = 20;
        tmrDrawVehicles.Enabled = true;
        tmrDrawVehicles.Tick += new EventHandler(tmrDrawVehiclesTick);

        Text = "Road Traffic Monitoring";
        Load += new EventHandler(LoadForm);
        StartPosition = FormStartPosition.CenterScreen;
        ClientSize = new System.Drawing.Size(1466, 924);
    }

    private void LoadForm(object sender, EventArgs e)
    {
        imgBackground = Image.FromFile("RoadMap2.jpg");
        imgLRVehicle1 = Image.FromFile("LRVehicle.png");
        imgLRVehicle2 = Image.FromFile("LRVehicle.png");

        LeftRightSpeed1 = 1.00F;
        LeftRightSpeed2 = 1.00F;

        xLeftRightVehicle1 = -10.00F;
        yLeftRightVehicle1 = 484.00F;
        xLeftRightVehicle2 = -10.00F;
        yLeftRightVehicle2 = 440.00F;
    }

    private void pbxRoadMapPaint(object sender, PaintEventArgs e)
    {
        e.Graphics.DrawImage(imgBackground, 0, 0);
        e.Graphics.DrawImage(imgLRVehicle1, xLeftRightVehicle1, yLeftRightVehicle1);
        e.Graphics.DrawImage(imgLRVehicle2, xLeftRightVehicle2, yLeftRightVehicle2);
    }

    private void DrawLeftRightVehicle1()
    {
        if ((xLeftRightVehicle1 >= 250.00F) && (xLeftRightVehicle1 <= 425.00F))
            thLeftRightVehicle1.Priority = ThreadPriority.Highest;
        else if ((xLeftRightVehicle1 > 550.00F) && (xLeftRightVehicle1 <= 845.00F))
            thLeftRightVehicle1.Priority = ThreadPriority.Lowest;
        else
            thLeftRightVehicle1.Priority = ThreadPriority.Normal;

        if (thLeftRightVehicle1.Priority == ThreadPriority.Highest)
            LeftRightSpeed1 = 3.50F;
        else if (thLeftRightVehicle1.Priority == ThreadPriority.Lowest)
            LeftRightSpeed1 = .50F;
        else
            LeftRightSpeed1 = 1.00F;

        if (xLeftRightVehicle1 < pbxRoadMap.Width)
            xLeftRightVehicle1 += LeftRightSpeed1;
        else
            xLeftRightVehicle1 = -100;
    }

    private void DrawLeftRightVehicle2()
    {
        if ((xLeftRightVehicle2 >= 450.00F) && (xLeftRightVehicle2 <= 550.00F))
            thLeftRightVehicle2.Priority = ThreadPriority.Highest;
        else if ((xLeftRightVehicle2 > 850.00F) && (xLeftRightVehicle2 <= 1020.00F))
            thLeftRightVehicle2.Priority = ThreadPriority.BelowNormal;
        else
            thLeftRightVehicle2.Priority = ThreadPriority.Normal;

        if (thLeftRightVehicle2.Priority == ThreadPriority.Highest)
            LeftRightSpeed2 = 4.25F;
        else if (thLeftRightVehicle2.Priority == ThreadPriority.BelowNormal)
            LeftRightSpeed2 = .35F;
        else
            LeftRightSpeed2 = 1.50F;

        if (xLeftRightVehicle2 < pbxRoadMap.Width)
            xLeftRightVehicle2 += LeftRightSpeed2;
        else
            xLeftRightVehicle2 = -100;
    }

    private void tmrDrawVehiclesTick(object sender, EventArgs e)
    {
        thLeftRightVehicle1 = new Thread(new ThreadStart(DrawLeftRightVehicle1));
        thLeftRightVehicle1.Priority = ThreadPriority.Normal;

        thLeftRightVehicle2 = new Thread(new ThreadStart(DrawLeftRightVehicle2));
        thLeftRightVehicle2.Priority = ThreadPriority.Highest;

        thLeftRightVehicle1.Start();
        thLeftRightVehicle2.Start();

        pbxRoadMap.Invalidate();
    }

    [STAThread]
    public static int Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Exercise());

        return 0;
    }
}

The Current Thread

To let you know the thread that is currently being used, the Thread class is equipped with a read-only property named CurrentThread:

public static Thread CurrentThread { get; }

A Background Thread

All of the threads we have created so far are referred to as foreground threads. They are used to run an application (a process), to start and/or to end it. In fact, depending on how it is created and is configured, a thread can prevent a user from closing an application. A background thread is a thread that works behind-the-scenes in an application. One of the main characteristics of a background thread is that it cannot interfere with the user trying to close the application.

A background thread is primarily created like a foreground thread. To let you control the grounding status of a thread, the Thread class is equipped with a Boolean property named IsBackground:

public bool IsBackground { get; set; }

As you can see, this is a read-write property that allows you either to specify that a thread is, or must behave as, a background thread, or to find out whether a thread is a background one. In the following example, we have a small vehicle and a big truck moving on a highway. The small car is naturally moving faster than the truck. The moving operations of each vehicle are controlled by a thread assigned to it. At one time, if the small car finds itself behind the truck, it may want to pass. To control the passing, when to pass, and how to pass, we will use a background thread. Our traffic road uses four lanes. The small vehicle can use all four lanes. The big truck will use the most left lane. This means that to pass, the small vehicle will always have a left lane available. If both vehicles are on the same lane, the background thread checks the distance between both cars. If the small car is approaching the truck in front and the distance becomes too short, the small vehicle would move to the empty left lane.

Practical LearningPractical Learning: Using a Background Thread

  1. Save the following pictures to your computer:

    Terminating a Thread

    Terminating a Thread Terminating a Thread Terminating a Thread
  2. Start Microsoft Visual Studio and create a Windows Forms application named Type RoadTrafficMonitoring2
  3. In the Solution Explorer, right-click Form1.cs and click Rename
  4. Type TrafficMonitoring (to get TrafficMonitoring.cs)
  5. Press Enter twice (once to indicate that you want to rename the form, second to accept the new name)
  6. In the Properties window, change the following values of the form:
    FormBorderStyle: FixedDialog
    Text:            Road Traffic Monitoring
    MaximizeBox:     False
  7. Double-click the middle of the form to launch its Load event
  8. Click the TrafficMonitoring.cs [Design] tab to return to the form
  9. In the Common Controls section of the Toolbox, click PictureBox and click the form
  10. In the Properties window, change its characteristics as follows:
    SizeMode: AutoSize
    (Name):   pbxRoadMap
  11. Still in the Properties window, click the Image field and click its ellipsis button
  12. In the Select Resource dialog box, click Import
  13. From the folder where you saved the above pictures, click one of them to select it. Press and hold Ctrl. Then click each of the other pictures. Release Ctrl and click Open
  14. In the Select Resource dialog box, click RoadMap1 to select it
  15. Click OK
  16. While the picture box is still selected on the form, in the Properties window, click the Events button Events
  17. Double-click Paint
  18. Click the TrafficMonitoring.cs [Design] tab to return to the form

    Traffic Road Monitoring

  19. On the Toolbox, click Components to expand it, then click Timer and click the form
  20. In the Properties window, change the following characteristics:
    Enabled:  True
    Interval: 10
    (Name):   tmrTrafficMonitoring
  21. Below the form, double-click tmrTrafficMonitoring to launch its Tick event
  22. Change the document as follows:
    using System.Threading;
    
    namespace RoadTrafficMonitoring2
    {
        public enum LanePosition { Behind, Even, Ahead };
    
        public partial class TrafficMonitoring : Form
        {
            bool vehicleIsPassing;
            private LanePosition position;
    
            Thread thVehicle1 = null;
            Thread thVehicle2 = null;
            Thread thEvaluator = null;
    
            float xVehicle1, yVehicle1;
            float xVehicle2, yVehicle2;
            float speedVehicle1, speedVehicle2;
    
            Image imgVehicle1, imgVehicle2;
    
            public TrafficMonitoring()
            {
                InitializeComponent();
            }
    
            private void TrafficMonitoring_Load(object sender, EventArgs e)
            {
                // Small Vehicle Initialization
                // At this time, the small car is not passing
                vehicleIsPassing = false;
    
                xVehicle1 = -10.00F;
    
                // Since our highway has four lanes, we will use a random number out of 4
                // to position the small car on one of the lanes
                Random rndNumber = new Random();
    
                int yVehicle = rndNumber.Next(1, 5);
    
                if (yVehicle == 1)
                    yVehicle1 = 160.00F;
                else if (yVehicle == 2)
                    yVehicle1 = 219.00F;
                else if (yVehicle == 3)
                    yVehicle1 = 279.00F;
                else
                    yVehicle1 = 335.00F;
    
                speedVehicle1 = 1.00F;
    
                // To start, we will position the small car behind the truck
                position = LanePosition.Behind;
                imgVehicle1 = Properties.Resources.Vehicle1; // Image.FromFile("Vehicle1.png");
    
                // Truck Initialization
                xVehicle2 = 425.00F;
    
                yVehicle = rndNumber.Next(1, 4);
    
                if (yVehicle == 1)
                    yVehicle2 = 219.00F;
                else if (yVehicle == 2)
                    yVehicle2 = 279.00F;
                else
                    yVehicle2 = 335.00F;
                
                speedVehicle2 = .35F;
    
                imgVehicle2 = Properties.Resources.Vehicle3; // Image.FromFile("Vehicle3.png");
            }
    
            private void pbxRoadMap_Paint(object sender, PaintEventArgs e)
            {
                e.Graphics.DrawImage(imgVehicle1, xVehicle1, yVehicle1);
                e.Graphics.DrawImage(imgVehicle2, xVehicle2, yVehicle2);
            }
    
            void ControlVehicle1()
            {
                // Whenever a vehicle reaches the right side of the screen, we will
                // reposition it to the beginning (the left side of the screen)
                if (xVehicle1 > pbxRoadMap.Width)
                {
                    vehicleIsPassing = false;
    
                    Random rndNumber = new Random();
    
                    xVehicle1 = -10.00F;
    
                    int xVehicle = rndNumber.Next(1, 5);
    
                    if (xVehicle == 1)
                        yVehicle1 = 160.00F;
                    else if (xVehicle == 2)
                        yVehicle1 = 219.00F;
                    else if (xVehicle == 3)
                        yVehicle1 = 279.00F;
                    else
                        yVehicle1 = 335.00F;
    
                    speedVehicle1 = 1.00F;
                    position = LanePosition.Behind;
                }
    
                xVehicle1 += speedVehicle1;
            }
    
            private void ControlVehicle2()
            {
                if (xVehicle2 > pbxRoadMap.Width)
                {
                    xVehicle2 = -10.00F;
    
                    Random rndNumber = new Random();
                    int xVehicle = rndNumber.Next(1, 4);
    
                    if (xVehicle == 1)
                        yVehicle2 = 219.00F;
                    else if (xVehicle == 2)
                        yVehicle2 = 279.00F;
                    else
                        yVehicle2 = 335.00F;
    
                    speedVehicle2 = .35F;
                    imgVehicle2 = Properties.Resources.Vehicle2; // Image.FromFile("Vehicle2.png");
                }
    
                xVehicle2 += speedVehicle2;
            }
    
            private void ControlVehiclePassing()
            {
                /* Get the rectangular boundaries of each vehicle.
                   Actually, we are insterested to know how close the small vehicle is 
                   to the truck. So we blow the rectangle a little bit*/
                RectangleF rectVehicle1 = new RectangleF(xVehicle1 + 100.00F, yVehicle1, imgVehicle1.Width + 20.00F, imgVehicle1.Height + 8.00F);
                RectangleF rectVehicle2 = new RectangleF(xVehicle2, yVehicle2 - 25, imgVehicle2.Width + 28.00F, imgVehicle2.Height + 2.00F);
    
                // First find out whether both vehicles are in the same lane
                if (Math.Abs(yVehicle1 - yVehicle2) <= 0.40F) // Both vehicles are in the same lane
                {
                    /* Since both vehicles are in the same lane, find out
                       whether the same vehicle is behind the truck, or they are even*/
                    if ((xVehicle1 + imgVehicle1.Width) < xVehicle2)
                        position = LanePosition.Behind;
                    else if ((xVehicle1 >= xVehicle2) && ((xVehicle1 + imgVehicle1.Width) <= xVehicle2))
                        position = LanePosition.Even;
                    else
                    {
                        speedVehicle1 = 1.00F;
                        speedVehicle2 = .35F;
                        position = LanePosition.Ahead;
                    }
    
                    // If the vehicles are in the same lane, the small vehicle probably wants to pass
                    vehicleIsPassing = true;
                }
    
                /* If:
                   a) The small car is behind the truck
                   b) The small car wants to pass
                   c) The small car is now (getting too) close to the truck
                   . . . */
                if( (position == LanePosition.Behind) && (vehicleIsPassing == true) && (rectVehicle1.IntersectsWith(rectVehicle2)))
                {
                    // . . . move the vehicle to the (empty) left lane
                    xVehicle1 += 1.25F;
                    yVehicle1 -= 0.85F;
                }
            }
    
            private void tmrMonitoring_Tick(object sender, EventArgs e)
            {
                thVehicle1 = new Thread(new ThreadStart(ControlVehicle1));
                thVehicle2 = new Thread(new ThreadStart(ControlVehicle2));
                thEvaluator = new Thread(new ThreadStart(ControlVehiclePassing));
    
                 thEvaluator.IsBackground = true;
    
                thVehicle1.Start();
                thVehicle2.Start();
                thEvaluator.Start();
    
                pbxRoadMap.Invalidate();
            }
        }
    }
  23. To execute, press Ctrl + F5. Observe the form until you see the small car passing the truck

    Traffic Road Monitoring

    Traffic Road Monitoring

    Traffic Road Monitoring

  24. Close the form and return to Microsoft Visual Studio

The Status of a Thread

At any time, you may want to know what is going on with a certain thread. This information is considered the status of the thread. To keep track of the ongoing or current status of a thread, the Thread class is equipped with the read-only ThreadState property, which is based on the ThreadState enumeration:

public ThreadState ThreadState { get; }

Use this property to know the current status of the thread. The members of the ThreadState enumeration are:

When checking the status of a thread, you can use the bitwise operator "|" which allows you to combine members. For example ThreadState.Background | ThreadState.Running indicates that you want to know whether the thread is a background thread and is currently active. Of course, you should not try to combine opposite values such as Aborted and Running.

A Pool of Threads

If you are using the Win32 library, you can create many threads each by calling the CreateThread() function. A group of threads created in Win32 is referred to as an unmanaged pool of threads. On the other hand and seen above, you can use the Thread class in the .NET Framework to create one thread at a time and have as many threads as possible. A collection of threads created using the .NET Framework is referred to as a managed pool thread.

Besides the ability to declare as many Thread variables as necessary, the .NET Framework provides the ThreadPool class used to create a collection of threads. Whether you use many Thread variables or the ThreadPool class, to let you find out whether a thread belongs to the managed thread pool, the Thread class is equipped with a read-only Boolean property named IsThreadPoolThread:

public bool IsThreadPoolThread { get; }

Managing the Execution of a Thread

Passing an Object to a Thread

The Thread class has another constructor that allows you to pass values to a thread. Its syntax is:

public Thread(ParameterizedThreadStart start);

This constructor takes a ParameterizedThreadStart delegate that is a method that takes an object as argument:

public delegate void ParameterizedThreadStart(object obj);

Here is an example of using this constructor:

ParameterizedThreadStart tsLeftRightVehicle = new ParameterizedThreadStart(DrawLeftRightVehicle);
Thread thLeftRightVehicle = new Thread(tsLeftRightVehicle);

To actually let you pass the object to the thread, the Thread class has another version of the Start() method whose syntax is:

public void Start(object parameter);

This means that, when calling the Thread.Start() method, pass your object as argument. Here is an example:

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

public class Exercise : System.Windows.Forms.Form
{
    private System.Windows.Forms.PictureBox pbxRoadMap;
    private System.Windows.Forms.Timer tmrDrawVehicles;

    private int xLeftRightVehicle, yLeftRightVehicle;
    private int xRightLeftVehicle, yRightLeftVehicle;

    private Image imgBackground;
    private Image imgLRVehicle, imgRLVehicle;

    public Exercise()
    {
        InitializeComponent();
    }

    private void InitializeComponent()
    {
        pbxRoadMap = new System.Windows.Forms.PictureBox();
        pbxRoadMap.Dock = DockStyle.Fill;
        pbxRoadMap.Paint += new PaintEventHandler(pbxRoadMapPaint);
        Controls.Add(pbxRoadMap);

        tmrDrawVehicles = new System.Windows.Forms.Timer();
        tmrDrawVehicles.Interval = 20;
        tmrDrawVehicles.Enabled = true;
        tmrDrawVehicles.Tick += new EventHandler(tmrDrawVehiclesTick);

        Text = "Road Traffic Monitoring";
        StartPosition = FormStartPosition.CenterScreen;
        ClientSize = new System.Drawing.Size(1466, 924);

        imgBackground = Image.FromFile("RoadMap1.jpg");
        imgLRVehicle = Image.FromFile("LRVehicle.png");
        imgRLVehicle = Image.FromFile("RLVehicle.png");

        Random rndNumber = new Random();

        xLeftRightVehicle = rndNumber.Next(0, pbxRoadMap.Width);
        yLeftRightVehicle = 484;
        xRightLeftVehicle = rndNumber.Next(0, pbxRoadMap.Width - 125);
        yRightLeftVehicle = 440;
    }

    private void pbxRoadMapPaint(object sender, PaintEventArgs e)
    {
        e.Graphics.DrawImage(imgBackground, 0, 0);
        e.Graphics.DrawImage(imgLRVehicle, xLeftRightVehicle, yLeftRightVehicle);
        e.Graphics.DrawImage(imgRLVehicle, xRightLeftVehicle, yRightLeftVehicle);
    }

    private void DrawLeftRightVehicle(object info)
    {
        if ((xLeftRightVehicle == 250) || (xLeftRightVehicle == 635) || (xLeftRightVehicle == 1019))
        {
            VehicleDetails details = info as VehicleDetails;

            Text = "Road Traffic Monitoring - Stop: " +
                   details.Make + " " + details.Model + " (" + details.Year.ToString() + ")";

            Thread.Sleep(5000);
        }
        else
            Text = "Road Traffic Monitoring";

        if (xLeftRightVehicle < pbxRoadMap.Width)
            xLeftRightVehicle++;
        else
            xLeftRightVehicle = -100;
    }

    private void DrawRightLeftVehicle()
    {
        if ((xRightLeftVehicle == 430) || (xRightLeftVehicle == 816) || (xRightLeftVehicle == 1199) )
        {
            Thread.Sleep(4500);
        }

        if (xRightLeftVehicle < -100)
            xRightLeftVehicle = pbxRoadMap.Width;
        else
            xRightLeftVehicle--;
    }

    private void tmrDrawVehiclesTick(object sender, EventArgs e)
    {
        VehicleDetails vLeftRight = new VehicleDetails();

        vLeftRight.Make  = "Ford";
        vLeftRight.Model = "Focus";
        vLeftRight.Year  = 2010;

        ParameterizedThreadStart tsLeftRightVehicle = new ParameterizedThreadStart(DrawLeftRightVehicle);
        Thread thLeftRightVehicle = new Thread(tsLeftRightVehicle);

        ThreadStart tsRightLeftVehicle = new ThreadStart(DrawRightLeftVehicle);
        Thread thRightLeftVehicle = new Thread(tsRightLeftVehicle);

        thLeftRightVehicle.Start(vLeftRight);
        thRightLeftVehicle.Start();

        pbxRoadMap.Invalidate();
    }

    public class VehicleDetails
    {
        public string Make  { get; set; }
        public string Model { get; set; }
        public int    Year  { get; set; }
    }

    [STAThread]
    public static int Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Exercise());

        return 0;
    }
}

Blocking a Thread

In some cases, you may have one thread whose activities are interferring with the activities of another or other threads. One action you can take is to block the interfering thread to allow other threads to perform their assignments. To support this operation, the Thread class is equipped with an overloaded method named Join. The simplest version uses the following syntax:

public void Join();

This versions allows one thread to block another thread. The other two versions use the following syntaxes:

public bool Join(int millisecondsTimeout);
public bool Join(TimeSpan timeout);

Both versions take a number of seconds as argument. As a result, the thread that calls this method would be blocked for that amount of time.

Practical LearningPractical Learning: Blocking a Thread

  1. In Microsoft Visual Studio, open the RoadTrafficMonitoring1 project created in the previous lesson
  2. Change the document as follows:
    using System;
    using System.Drawing;
    using System.Threading;
    using System.Windows.Forms;
    
    namespace RoadTrafficMonitoring1
    {
        public partial class TrafficMonitoring : Form
        {
            int xLeftRightVehicle, yLeftRightVehicle;
            int xRightLeftVehicle, yRightLeftVehicle;
            int xTopBottomVehicle1, yTopBottomVehicle1;
            int xTopBottomVehicle2, yTopBottomVehicle2;
            int xTopBottomVehicle3, yTopBottomVehicle3;
            int xBottomTopVehicle1, yBottomTopVehicle1;
            int xBottomTopVehicle2, yBottomTopVehicle2;
            int xBottomTopVehicle3, yBottomTopVehicle3;
    
            Thread thLeftRightVehicle;
            Thread thRightLeftVehicle;
            Thread thTopBottomVehicle1;
            Thread thTopBottomVehicle2;
            Thread thTopBottomVehicle3;
            Thread thBottomTopVehicle1;
            Thread thBottomTopVehicle2;
            Thread thBottomTopVehicle3;
    
            public TrafficMonitoring()
            {
                InitializeComponent();
            }
    
            private void TrafficMonitoring_Load(object sender, EventArgs e)
            {
                xLeftRightVehicle  =    0;
                yLeftRightVehicle  =  536;
                xRightLeftVehicle  = 1510;
                yRightLeftVehicle  =  492;
                xTopBottomVehicle1 =  365;
                yTopBottomVehicle1 =   38;
                xTopBottomVehicle2 =  768;
                yTopBottomVehicle2 =  154;
                xTopBottomVehicle3 = 1165; 
                yTopBottomVehicle3 =  332;
                xBottomTopVehicle1 =  410;
                yBottomTopVehicle1 =  650;
                xBottomTopVehicle2 =  810;
                yBottomTopVehicle2 =  765;
                xBottomTopVehicle3 = 1210;
                yBottomTopVehicle3 =  965;
            }
    
            private void pbRoadMap_Paint(object sender, PaintEventArgs e)
            {
                pbxLRVehicle.Location  = new Point(xLeftRightVehicle,  yLeftRightVehicle);
                pbxRLVehicle.Location  = new Point(xRightLeftVehicle,  yRightLeftVehicle);
                pbxTBVehicle1.Location = new Point(xTopBottomVehicle1, yTopBottomVehicle1);
                pbxTBVehicle2.Location = new Point(xTopBottomVehicle2, yTopBottomVehicle2);
                pbxTBVehicle3.Location = new Point(xTopBottomVehicle3, yTopBottomVehicle3);
                pbxBTVehicle1.Location = new Point(xBottomTopVehicle1, yBottomTopVehicle1);
                pbxBTVehicle2.Location = new Point(xBottomTopVehicle2, yBottomTopVehicle2);
                pbxBTVehicle3.Location = new Point(xBottomTopVehicle3, yBottomTopVehicle3);
            }
    
            void DrawLeftRightVehicle()
            {
                /* If the car comes to a Stop sign, 
                 * it must stop/wait for 5 seconds before continuing. */
                if ((xLeftRightVehicle == 286) || (xLeftRightVehicle == 688) || (xLeftRightVehicle == 1086))
                    Thread.Sleep(5000);
    
                if ( xLeftRightVehicle < 1535 )
                    xLeftRightVehicle += 2;
                else
                    xLeftRightVehicle = 0;
            }
    
            void DrawRightLeftVehicle()
            {
                // If the car comes to a Stop sign, it must
                // stop/wait for 5 seconds before continuing
                TimeSpan waitTime = new TimeSpan(0, 0, 5);
    
                if ((xRightLeftVehicle == 450) || (xRightLeftVehicle == 850) || (xRightLeftVehicle == 1250))
                    Thread.Sleep(waitTime);
    
                if ( xRightLeftVehicle < 0 )
                    xRightLeftVehicle = 1510;
                else
                    xRightLeftVehicle--;
            }
    
            void DrawTopBottomVehicle1()
            {
                /* If the car comes to a Stop sign, 
                 * it must stop/wait for 7.5 seconds before continuing. */
                if (yTopBottomVehicle1 == 414)
                    thTopBottomVehicle1.Join(7500);
    
                if ( yTopBottomVehicle1 < 985 )
                    yTopBottomVehicle1++;
                else
                    yTopBottomVehicle1 = 30;
            }
            
            void DrawTopBottomVehicle2()
            {
                if (yTopBottomVehicle2 == 416)
                    thTopBottomVehicle2.Join(4200);
    
                if ( yTopBottomVehicle2 < 988 )
                    yTopBottomVehicle2++;
                else
                    yTopBottomVehicle2 = 30;
            }
    
            void DrawTopBottomVehicle3()
            {
                if (yTopBottomVehicle3 == 410)
                    thTopBottomVehicle3.Join(5200);
    
                if ( yTopBottomVehicle3 < 985 )
                    yTopBottomVehicle3 += 2;
                else
                    yTopBottomVehicle3 = 30;
            }
    
            void DrawBottomTopVehicle1()
            {
                TimeSpan waitTime = new TimeSpan(0, 0, 5);
    
                if (yBottomTopVehicle1 == 576)
                    thBottomTopVehicle1.Join(waitTime);
    
                if ( yBottomTopVehicle1 < 30 )
                    yBottomTopVehicle1 = 988;
                else
                    yBottomTopVehicle1 -= 2;
            }
    
            void DrawBottomTopVehicle2()
            {
                TimeSpan waitTime = new TimeSpan(0, 0, 12);
    
                if ((yBottomTopVehicle2 == 574) || (yBottomTopVehicle2 == 576))
                    thBottomTopVehicle2.Join(waitTime);
    
                if ( yBottomTopVehicle2 < 30 )
                    yBottomTopVehicle2 = 988;
                else
                    yBottomTopVehicle2 -= 3;
            }
    
            void DrawBottomTopVehicle3()
            {
                TimeSpan waitTime = new TimeSpan(0, 0, 8);
    
                if (yBottomTopVehicle3 == 576)
                    thBottomTopVehicle3.Join(waitTime);
    
                if ( yBottomTopVehicle3 < 30 )
                    yBottomTopVehicle3 = 988;
                else
                    yBottomTopVehicle3--;
            }
    
            private void tmrTrafficMonitoring_Tick(object sender, EventArgs e)
            {
                ThreadStart tsLeftRightVehicle = new ThreadStart(DrawLeftRightVehicle);
                thLeftRightVehicle = new Thread(tsLeftRightVehicle);
    
                ThreadStart tsRightLeftVehicle = new ThreadStart(DrawRightLeftVehicle);
                thRightLeftVehicle = new Thread(tsRightLeftVehicle);
    
                thTopBottomVehicle1 = new Thread(new ThreadStart(DrawTopBottomVehicle1));
                thTopBottomVehicle2 = new Thread(new ThreadStart(DrawTopBottomVehicle2));
                thTopBottomVehicle3 = new Thread(new ThreadStart(DrawTopBottomVehicle3));
                thBottomTopVehicle1 = new Thread(new ThreadStart(DrawBottomTopVehicle1));
                thBottomTopVehicle2 = new Thread(new ThreadStart(DrawBottomTopVehicle2));
                thBottomTopVehicle3 = new Thread(new ThreadStart(DrawBottomTopVehicle3));
    
                thLeftRightVehicle.Start();
                thRightLeftVehicle.Start();
                thTopBottomVehicle1.Start();
                thTopBottomVehicle2.Start();
                thTopBottomVehicle3.Start();
                thBottomTopVehicle1.Start();
                thBottomTopVehicle2.Start();
                thBottomTopVehicle3.Start();
    
                pbxRoadMap.Invalidate();
            }
        }
    }
  3. To execute, press Ctrl + F5
  4. Close the form and return to Microsoft Visual Studio

Threading and Object-Oriented Programming

Object-oriented programming is the ability to programmatically create and describe objects. One or more objects may be used. Whether it is one or more, a common set of characteristics is used to describe each object. Those common characteristics are grouped in a class (or structure). The characteristics of a class are called properties (and/or fields). For example, a moving vehicle can be represented by such characteristics as its location, which can be represented by a point (Point or PointF structure) or x and y coordinates. Other details could include the picture of the car and/or its speed, which determines how fast (or slow) the vehicle is moving. While the characteristics describe an object, actions are what an object can do, such as moving or drawing itself.

Once the class or structure has been created, variables can be declared from it, and then the members of the class can be accessed.

Practical LearningPractical Learning: Creating a Threading Object

  1. Save the following pictures to your computer:

    Metro Map

    Left Right Moving Vehicle Right Left Moving Vehicle
    Top-Bottom Moving Moving Vehicle Top-Bottom Moving Vehicle Top-Bottom Moving Vehicle
    Top-Bottom Moving Moving Vehicle Top-Bottom Moving Vehicle Top-Bottom Moving Vehicle
  2. In Microsoft Visual Studio, start a new Windows Forms application named RoadTrafficMonitoring3
  3. In the Solution Explorer, right-click Form1.cs and click Rename
  4. Type TrafficMonitoring (to get TrafficMonitoring.cs)
  5. Press Enter twice (once to indicate that you want to rename the form, second to accept the new name)
  6. In the Properties window, change the following values of the form:
    Text:        Road Traffic Monitoring
    Size:        1608,1050
    MaximizeBox: False
  7. Double-click the middle of the form to launch its Load event
  8. Click the TrafficMonitoring.cs [Design] tab to return to the form
  9. In the Common Controls section of the Toolbox, click PictureBox and click the form
  10. In the Properties window, change its characteristics as follows:
    SizeMode: AutoSize
    (Name):   pbxRoadMap
    Location: 0,0
  11. Still in the Properties window, click Image and click its ellipsis button
  12. In the Select Resource dialog box, click Import
  13. From the folder where you saved the above pictures, click one of them to select it. Press and hold Ctrl. Then click each of the other pictures. Release Ctrl and click Open
  14. In the Select Resource dialog box, click RoadMap1 to select it
  15. Click OK

    Viewing Data in a View

  16. While the picture box is still selected on the form, in the Properties window, click the Events button Events
  17. Double-click Paint
  18. Click the TrafficMonitoring.cs [Design] tab to return to the form
  19. To create a new file, in the Solution Explorer, right-click RoadTrafficMonitoring3 -> Add -> New Item...
  20. In the left list of the Add New Item dialog box,under Visual C# Items, click Code and, in the middle list, click Interface
  21. Change the File Name to Vehicle
  22. Click Add
  23. Change the document as follows:
    using System.Drawing;
    
    namespace RoadTrafficMonitoring3
    {
        public interface IVehicle
        {
            float X       { get; set; }
            float Y       { get; set; }
            Image Picture { get; set; }
            void Draw(Graphics graph);
        }
    }
  24. To create a class, on the main menu, click Project -> Add Class
  25. Change the file name to Car
  26. Click Add
  27. Change the document as follows:
    using System.Drawing;
    using RoadTrafficMonitoring3.Properties;
    
    namespace RoadTrafficMonitoring3
    {
        public class Car : IVehicle
        {
            public float X       { get; set; }
            public float Y       { get; set; }
            public Image Picture { get; set; }
    
            public Car()
            {
                X = 0.00F;
                Y = 0.00F;
                Picture = Resources.LRVehicle;
            }
    
            public Car(float x, float y, Image picture)
            {
                X = x;
                Y = y;
                Picture = picture;
            }
    
            public void Draw(Graphics graph)
            {
                graph.DrawImage(Picture, X, Y);
            }
        }
    }
  28. Click the TrafficMonitoring.cs [Design] tab to return to the form
  29. On the Toolbox, click Components to expand it, then click Timer and click the form
  30. In the Properties window, change the following characteristics:
    Enabled:  True
    Interval: 10
    (Name):   tmrTrafficMonitoring
  31. Below the form, double-click tmrTrafficMonitoring to launch its Tick event
  32. Change the document as follows:
    using System;
    using System.Threading;
    using System.Windows.Forms;
    using RoadTrafficMonitoring3.Properties;
    
    namespace RoadTrafficMonitoring3
    {
        public partial class TrafficMonitoring : Form
        {
            IVehicle LeftRight, RightLeft;
            IVehicle TopBottom1, TopBottom2, TopBottom3;
            IVehicle BottomTop1, BottomTop2, BottomTop3;
    
            static Thread thLeftRightVehicle = null;
            static Thread thRightLeftVehicle = null;
            static Thread thTopBottomVehicle1 = null;
            static Thread thTopBottomVehicle2 = null;
            static Thread thTopBottomVehicle3 = null;
            static Thread thBottomTopVehicle1 = null;
            static Thread thBottomTopVehicle2 = null;
            static Thread thBottomTopVehicle3 = null;
    
            public TrafficMonitoring()
            {
                InitializeComponent();
            }
    
            private void TrafficMonitoring_Load(object sender, EventArgs e)
            {
                Random rndNumber = new Random();
    
                LeftRight = new Car(rndNumber.Next(0, pbxRoadMap.Width), 506, Resources.LRVehicle);
                RightLeft = new Car(rndNumber.Next(0, pbxRoadMap.Width - 125), 460, Resources.RLVehicle);
    
                TopBottom1 = new Car( 362, rndNumber.Next(0, pbxRoadMap.Height), Resources.TBVehicle1);
                TopBottom2 = new Car( 764, rndNumber.Next(0, pbxRoadMap.Height), Resources.TBVehicle2);
                TopBottom3 = new Car(1162, rndNumber.Next(0, pbxRoadMap.Height), Resources.TBVehicle3);
                BottomTop1 = new Car( 406, rndNumber.Next(pbxRoadMap.Height - 95), Resources.BTVehicle1);
                BottomTop2 = new Car( 808, rndNumber.Next(pbxRoadMap.Height - 88), Resources.BTVehicle2);
                BottomTop3 = new Car(1208, rndNumber.Next(pbxRoadMap.Height - 90), Resources.BTVehicle3);
    
                //imgBackground = Resources.RoadMap1;
            }
    
            private void pbxRoadMap_Paint(object sender, PaintEventArgs e)
            {
                LeftRight.Draw(e.Graphics);
                RightLeft.Draw(e.Graphics);
                TopBottom1.Draw(e.Graphics);
                TopBottom2.Draw(e.Graphics);
                TopBottom3.Draw(e.Graphics);
                BottomTop1.Draw(e.Graphics);
                BottomTop2.Draw(e.Graphics);
                BottomTop3.Draw(e.Graphics);
            }
    
            void DrawLeftRightVehicle()
            {
                if ((LeftRight.X == 264) || (LeftRight.X == 665) || (LeftRight.X == 1066))
                    Thread.Sleep(5000);
    
                if (LeftRight.X < pbxRoadMap.Width)
                    LeftRight.X++;
                else
                    LeftRight.X = -100;
            }
    
            void DrawRightLeftVehicle()
            {
                if ((RightLeft.X == 448) || (RightLeft.X == 852) || (RightLeft.X == 1246))
                    Thread.Sleep(4500);
    
                if (RightLeft.X < -100)
                    RightLeft.X = pbxRoadMap.Width;
                else
                    RightLeft.X--;
            }
    
            void DrawTopBottomVehicle1()
            {
                if (TopBottom1.Y == 360)
                    thTopBottomVehicle1.Join(5000);
    
                if (TopBottom1.Y < pbxRoadMap.Height)
                    TopBottom1.Y++;
                else
                    TopBottom1.Y = -100;
            }
    
            void DrawTopBottomVehicle2()
            {
                if (TopBottom2.Y == 370)
                    thTopBottomVehicle2.Join(4200);
    
                if (TopBottom2.Y < pbxRoadMap.Height)
                    TopBottom2.Y++;
                else
                    TopBottom2.Y = -100;
            }
    
            void DrawTopBottomVehicle3()
            {
                if ((TopBottom3.Y >= 352) && (TopBottom3.Y <= 354))
                    thTopBottomVehicle3.Join(5200);
    
                if (TopBottom3.Y < pbxRoadMap.Height)
                    TopBottom3.Y += 2;
                else
                    TopBottom3.Y = -100;
            }
    
            void DrawBottomTopVehicle1()
            {
                if ((BottomTop1.Y >= 547) && (BottomTop1.Y <= 549))
                    thBottomTopVehicle1.Join(4600);
    
                if (BottomTop1.Y < -100)
                    BottomTop1.Y = pbxRoadMap.Height;
                else
                    BottomTop1.Y -= 2;
            }
    
            void DrawBottomTopVehicle2()
            {
                if ((BottomTop2.Y >= 544) && (BottomTop2.Y <= 547))
                    thBottomTopVehicle2.Join(4700);
    
                if (BottomTop2.Y < -100)
                    BottomTop2.Y = pbxRoadMap.Height;
                else
                    BottomTop2.Y -= 3;
            }
    
            void DrawBottomTopVehicle3()
            {
                if (BottomTop3.Y == 547)
                    thBottomTopVehicle3.Join(4950);
    
                if (BottomTop3.Y < -100)
                    BottomTop3.Y = pbxRoadMap.Height;
                else
                    BottomTop3.Y--;
            }
    
            private void tmrTrafficMonitoring_Tick(object sender, EventArgs e)
            {
                ThreadStart tsLeftRightVehicle = new ThreadStart(DrawLeftRightVehicle);
                thLeftRightVehicle = new Thread(tsLeftRightVehicle);
    
                ThreadStart tsRightLeftVehicle = new ThreadStart(DrawRightLeftVehicle);
                thRightLeftVehicle = new Thread(tsRightLeftVehicle);
    
                thTopBottomVehicle1 = new Thread(new ThreadStart(DrawTopBottomVehicle1));
                thTopBottomVehicle2 = new Thread(new ThreadStart(DrawTopBottomVehicle2));
                thTopBottomVehicle3 = new Thread(new ThreadStart(DrawTopBottomVehicle3));
                thBottomTopVehicle1 = new Thread(new ThreadStart(DrawBottomTopVehicle1));
                thBottomTopVehicle2 = new Thread(new ThreadStart(DrawBottomTopVehicle2));
                thBottomTopVehicle3 = new Thread(new ThreadStart(DrawBottomTopVehicle3));
    
                thLeftRightVehicle.Start();
                thRightLeftVehicle.Start();
                thTopBottomVehicle1.Start();
                thTopBottomVehicle2.Start();
                thTopBottomVehicle3.Start();
                thBottomTopVehicle1.Start();
                thBottomTopVehicle2.Start();
                thBottomTopVehicle3.Start();
    
                pbxRoadMap.Invalidate();
            }
        }
    }
  33. To execute the project, on the main menu of Microsoft Visual Studio, click Debug -> Start Without Debugging
  34. Close the form and return to Microsoft Visual Studio

Exceptionally Handling a Thread

Introduction

A thread is subject to many problems, including internal interruptions (one thread in an application may want to stop another thread of the same application from doing its work) and external conflicts (threads from different applications may want to access, and therefore compete for, the same resource). To deal with these and other types of problems, you should handle exceptions in the threads of your application.

As you should know from your knowledge of C#, the normal flow of code is handled in a try block. The errors or exceptions are handled in a catch block. The formula to follow is:

try {
    // Normal thread operation(s)
}
catch(argument)
{
    // Handling exception(s)
}

Of course, you can include as many catch blocks as you judge necessary.

Aggregating the Execution of an Application

As mentioned in our introduction, a thread is a small program that contributes to the execution of a process (also called an application). Something inside the thread may cause it to behave badly. For example, an internal calculation may go wrong. One thread may interfere, or try to interfere, with the job that another thread is trying to accomplish. These types of problems may interrupt a process or make the result of an application unpredictable. To assist you with these types of problems, the .NET Framework provides a class named AggregateException. This class includes many constructors with different goals.

The primary way to use the AggregateException exception is to pass it to the catch() clause. Here is an example:

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

public class Exercise : Form
{
    PictureBox pbxCanvas;

    int xPosition;
    int yPosition;

    Image imgLRVehicle;
    Image imgBackground;

    Timer tmrDrawVehicle;

    public Exercise()
    {
        InitializeComponent();
    }

    private void InitializeComponent()
    {
        pbxCanvas = new System.Windows.Forms.PictureBox();
        pbxCanvas.Dock = DockStyle.Fill;
        pbxCanvas.Paint += new PaintEventHandler(pbxCanvasPaint);
        Controls.Add(pbxCanvas);

        tmrDrawVehicle = new System.Windows.Forms.Timer();
        tmrDrawVehicle.Interval = 20;
        tmrDrawVehicle.Enabled = true;
        tmrDrawVehicle.Tick += new EventHandler(tmrDrawVehicleTick);

        Random rndNumber = new Random();

        xPosition = rndNumber.Next(0, 1024);
        yPosition = 484;

        imgBackground = Image.FromFile("RoadMap1.jpg");
        imgLRVehicle = Image.FromFile("LRVehicle1.png");

        Text = "Traffic Monitoring";

        StartPosition = FormStartPosition.CenterScreen;
        ClientSize = new System.Drawing.Size(1466, 924);
    }

    private void pbxCanvasPaint(object sender, PaintEventArgs e)
    {
        e.Graphics.DrawImage(imgBackground, 0, 0);
        e.Graphics.DrawImage(imgLRVehicle, xPosition, yPosition);
    }

    private void MoveVehicle()
    {
        if (xPosition < pbxCanvas.Width)
            xPosition++;
        else
            xPosition = -100;
    }

    private void tmrDrawVehicleTick(object sender, EventArgs e)
    {
        ThreadStart ts = new ThreadStart(MoveVehicle);
        Thread thVehicle = new Thread(ts);

        try
        {
            thVehicle.Start();
            pbxCanvas.Invalidate();
        }
        catch (AggregateException ae)
        {
            MessageBox.Show("Something went wrong and interferred with the application's " +
                            "execution. Please report the error as follows." + Environment.NewLine +
                            ae.Message, "Traffic Monitoring",
                            MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
    }

    public static int Main()
    {
        Application.EnableVisualStyles();
        Application.Run(new Exercise());

        return 0;
    }
}

One of the constructors of the AggregateException class allows you to specify its message by passing a string to the constructor. Its syntax is:

public AggregateException(string message)

After initializing an AggregateException object with this constructor, its argument becomes the message of the exception. Here is an example of using this constructor:

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

public class Exercise : Form
{
    PictureBox pbxCanvas;

    int xPosition;
    int yPosition;

    Image imgLRVehicle;
    Image imgBackground;

    Timer tmrDrawVehicle;

    public Exercise()
    {
        InitializeComponent();
    }

    private void InitializeComponent()
    {
        pbxCanvas = new System.Windows.Forms.PictureBox();
        pbxCanvas.Dock = DockStyle.Fill;
        pbxCanvas.Paint += new PaintEventHandler(pbxCanvasPaint);
        Controls.Add(pbxCanvas);

        tmrDrawVehicle = new System.Windows.Forms.Timer();
        tmrDrawVehicle.Interval = 20;
        tmrDrawVehicle.Enabled = true;
        tmrDrawVehicle.Tick += new EventHandler(tmrDrawVehicleTick);

        Random rndNumber = new Random();

        xPosition = rndNumber.Next(0, 1024);
        yPosition = 484;

        imgBackground = Image.FromFile("RoadMap1.jpg");
        imgLRVehicle = Image.FromFile("LRVehicle1.png");

        Text = "Traffic Monitoring";

        StartPosition = FormStartPosition.CenterScreen;
        ClientSize = new System.Drawing.Size(1466, 924);
    }

    private void pbxCanvasPaint(object sender, PaintEventArgs e)
    {
        e.Graphics.DrawImage(imgBackground, 0, 0);
        e.Graphics.DrawImage(imgLRVehicle, xPosition, yPosition);
    }

    private void MoveVehicle()
    {
        if (xPosition < pbxCanvas.Width)
            xPosition++;
        else
            xPosition = -100;
    }

    private void tmrDrawVehicleTick(object sender, EventArgs e)
    {
        ThreadStart ts = new ThreadStart(MoveVehicle);
        Thread thVehicle = new Thread(ts);

        try
        {
            thVehicle.Start();
            pbxCanvas.Invalidate();
        }
        catch (AggregateException ae)
        {
            ae = new AggregateException("Something went wrong and interferred with the application's execution.");
            
            MessageBox.Show("Please report the error as follows." + Environment.NewLine +
                            ae.Message, "Traffic Monitoring",
                            MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
    }

    public static int Main()
    {
        Application.EnableVisualStyles();
        Application.Run(new Exercise());

        return 0;
    }
}

An Inner Exception

During its lifetime, a thread can throw many exceptions and exceptions from other threads can affect it. Problems or exceptions can involve threads from the same application as well as threads from other applications. Exceptions can also be caused by (child) threads created inside of (parent) threads. An inner exception is an exception that causes another exception. To support inner exceptions, the AggregateException class inherits a property named InnerException from the Exception class.

A Collection of Exceptions

As one exception can cause another exception, many exceptions can cause an exception. The various exceptions that can cause an exception are treated as a collection. To let you get the collection of exceptions that have caused an exception, the AggregateException class includes a collection-based property named InnerExceptions:

public ReadOnlyCollection<Exception> InnerExceptions { get; }

To access each inner exception that caused an exception, you can use a foreach loop that will visit each item of the AggregateException object. To help you manage the exceptions that cause an exception, the AggregateException class includes various constructors that can be used to initialize an AggregateException object with a collection of exceptions related to its thread.

Handling Each Inner Exception

To assist you in handling an exception that is in the collection of the AggregateException exceptions, the AggregateException class includes a Boolean method named Handle. Its syntax is:

public void Handle(Func<Exception, bool> predicate)

This method takes an Exception object as argument and the function. The function returns a Boolean value that indicates whether the exception was actually handled. Here is an example of calling this method:

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

public class Exercise : Form
{
    PictureBox pbxCanvas;

    int xPosition;
    int yPosition;

    Image imgLRVehicle;
    Image imgBackground;

    Timer tmrDrawVehicle;

    public Exercise()
    {
        InitializeComponent();
    }

    void InitializeComponent()
    {
        pbxCanvas = new System.Windows.Forms.PictureBox();
        pbxCanvas.Dock = DockStyle.Fill;
        pbxCanvas.Paint += new PaintEventHandler(pbxCanvasPaint);
        Controls.Add(pbxCanvas);

        tmrDrawVehicle = new System.Windows.Forms.Timer();
        tmrDrawVehicle.Interval = 20;
        tmrDrawVehicle.Enabled = true;
        tmrDrawVehicle.Tick += new EventHandler(tmrDrawVehicleTick);

        Random rndNumber = new Random();

        xPosition = rndNumber.Next(0, 1024);
        yPosition = 484;

        imgBackground = Image.FromFile("RoadMap1.jpg");
        imgLRVehicle = Image.FromFile("LRVehicle1.png");

        Text = "Traffic Monitoring";

        StartPosition = FormStartPosition.CenterScreen;
        ClientSize = new System.Drawing.Size(1466, 924);
    }

    private void pbxCanvasPaint(object sender, PaintEventArgs e)
    {
        e.Graphics.DrawImage(imgBackground, 0, 0);
        e.Graphics.DrawImage(imgLRVehicle, xPosition, yPosition);
    }

    void MoveVehicle()
    {
        if (xPosition < pbxCanvas.Width)
            xPosition++;
        else
            xPosition = -100;
    }

    bool PresentError(Exception e)
    {
        MessageBox.Show("The current error occurred as follows: " + e.Message);

        return true;
    }

    void tmrDrawVehicleTick(object sender, EventArgs e)
    {
        ThreadStart ts = new ThreadStart(MoveVehicle);
        Thread thVehicle = new Thread(ts);

        try
        {
            thVehicle.Start();
            pbxCanvas.Invalidate();
        }
        catch (AggregateException ae)
        {
            ae.Handle(PresentError);
            
            MessageBox.Show("Something went wrong and interferred with the application's execution. " +
                            "Please report the error as follows." + Environment.NewLine +
                            ae.Message, "Traffic Monitoring",
                            MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
    }

    public static int Main()
    {
        Application.EnableVisualStyles();
        Application.Run(new Exercise());

        return 0;
    }
}

Of course, you don't have to first define the function. You can implement it directly where it is needed.

When a Thread Aborts

As we saw in the previous lesson, one way to request that a thread be interrupted is to call the Thread.Abort() method. When this method is called, the operating system (OS) is asked to decide whether to terminate the operations of a thread. At that time, the OS throws an exception named ThreadAbortException. This class has only one member as a property named ExceptionState that is of type object:

public object ExceptionState { get; }

This property actually holds the information you should have passed to the Thread.Abort() method. Remember that the Thread.Abort() method doesn't actually terminate a thread. Therefore, neither call the Thread.Abort() method nor handle a ThreadAbortException exception in code that is continually executing, such as code that runs in a timer.


Previous Copyright © 2016-2024, FunctionX Monday 04 September 2016 Next