Introduction to the Characteristics of a Thread
Introduction to the 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 a property named Name:
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:
namespace RoadTrafficMonitoring21 { public partial class Exercise : Form { Thread? thVehicle; int xPosition; int yPosition; public Exercise() { InitializeComponent(); } private void Exercise_Load(object sender, EventArgs e) { Random rndNumber = new Random(); xPosition = rndNumber.Next(0, 900); yPosition = 125; } void MoveVehicle() { if (xPosition < pbxRoadMap.Width - 50) xPosition++; else xPosition = 15; Text = thVehicle!.Name; } private void pbxRoadMap_Paint(object sender, PaintEventArgs e) { pbxLRVehicle.Location = new Point(xPosition, yPosition); } private void tmrDrawVehicles_Tick(object sender, EventArgs e) { ThreadStart tsVehicle = new ThreadStart(MoveVehicle); thVehicle = new Thread(tsVehicle); thVehicle.Name = "Traffic Monitoring"; thVehicle.Start(); pbxRoadMap.Invalidate(); } } }
Here is an example of what this would produce:
Practical Learning: Introducing the Characteristics of a Thread
FormBorderStyle: FixedDialog Text: Road Traffic Monitoring MaximizeBox: False
SizeMode: AutoSize (Name): pbxRoadMap
Enabled: True Interval: 10 (Name): tmrTrafficMonitoring
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):
namespace RoadTrafficMonitoring { public partial class Exercise : Form { 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 Exercise_Load(object sender, EventArgs e) { imgBackground = Image.FromFile("C:\\Resources\\RoadMap1.png"); imgLRVehicle1 = Image.FromFile("C:\\Resources\\LRVehicle.png"); imgLRVehicle2 = Image.FromFile("C:\\Resources\\LRVehicle.png"); LeftRightSpeed1 = 1.00F; LeftRightSpeed2 = 1.00F; xLeftRightVehicle1 = -10.00F; yLeftRightVehicle1 = 221.216F; xLeftRightVehicle2 = -10.00F; yLeftRightVehicle2 = 306.304F; } private void pbxRoadMap_Paint(object sender, PaintEventArgs e) { Graphics grapher = e.Graphics; grapher.DrawImage(imgBackground!, 0, 0); grapher.DrawImage(imgLRVehicle1!, xLeftRightVehicle1, yLeftRightVehicle1); grapher.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(); } } }
Here are examples of running the program:
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 Learning: Using a Background Thread
/* This project simulates a small portion of a road traffic. * The environment is represented by an image that contains a straight road with grass on both sides. * The road has four delimited lanes. The traffic will use a small car and a (big) truck. */ namespace RoadTrafficMonitoring2 { public enum LanePosition { Behind, Even, Ahead }; public partial class TrafficSystem : Form { private LanePosition position; private bool vehicleIsPassing; private Thread? thVehicle1 = null; private Thread? thVehicle2 = null; private Thread? thEvaluator = null; private float xVehicle1, yVehicle1; private float xVehicle2, yVehicle2; private float speedVehicle1, speedVehicle2; Image? imgVehicle1, imgVehicle2; public TrafficSystem() { InitializeComponent(); } private void TrafficSystem_Load(object sender, EventArgs e) { // Small Vehicle Initialization // As the application is starting, the small car is not passing vehicleIsPassing = false; // This is the initial horizontal position of the small vehicle xVehicle1 = -175.525F; /* When the application starts, 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(); // Choose a random position int yVehicle = rndNumber.Next(1, 4); // Specify the initial position of the small based on the gotten random number if (yVehicle == 1) yVehicle1 = 148.105F; else if (yVehicle == 2) yVehicle1 = 227.255F; else if (yVehicle == 3) yVehicle1 = 308.105F; else yVehicle1 = 384.225F; // This is the speed of the small vehicle speedVehicle1 = 1.00F; // To start, we will position the small car behind the truck position = LanePosition.Behind; imgVehicle1 = Properties.Resources.Vehicle1; // Truck Initialization xVehicle2 = 224.315F; // Get a random position for the truck yVehicle = rndNumber.Next(1, 4); // Specify the position of the truck based on the gotten random number if (yVehicle == 1) yVehicle2 = 227.255F; else if (yVehicle == 2) yVehicle2 = 308.105F; else yVehicle2 = 384.225F; // This is the speed of the truck speedVehicle2 = .35F; imgVehicle2 = Properties.Resources.Vehicle2; } 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 picture box (or form), we will reposition it to the beginning (the left side of the picture box or form) */ if (xVehicle1 > pbxRoadMap.Width) { vehicleIsPassing = false; Random rndNumber = new Random(); xVehicle1 = -355.525F; int xVehicle = rndNumber.Next(1, 5); if (xVehicle == 1) yVehicle1 = 148.105F; else if (xVehicle == 2) yVehicle1 = 227.255F; else if (xVehicle == 3) yVehicle1 = 308.105F; else yVehicle1 = 384.225F; speedVehicle1 = 1.00F; position = LanePosition.Behind; } xVehicle1 += speedVehicle1; } private void ControlVehicle2() { if (xVehicle2 > pbxRoadMap.Width) { xVehicle2 = -202.255F; Random rndNumber = new Random(); int xVehicle = rndNumber.Next(1, 4); if (xVehicle == 1) yVehicle2 = 227.255F; else if (xVehicle == 2) yVehicle2 = 308.105F; else yVehicle2 = 384.225F; speedVehicle2 = .35F; imgVehicle2 = Properties.Resources.Vehicle2; } 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 + 200.00F, yVehicle1, imgVehicle1!.Width + 40.00F, imgVehicle1.Height + 30.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 small 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 += 0.825F; yVehicle1 -= 1.125F; } } private void tmrTrafficMonitoring_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(); } } }
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; }
Practical Learning: Ending the Lesson
Previous | Copyright © 2016-2024, FunctionX | Monday 01 July 2024, 22:58 | Next |