The Characteristics of a Thread
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 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; using System.Drawing; using System.Threading; using System.Windows.Forms; namespace RoadTrafficMonitoring2 { 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; using System.Drawing; using System.Threading; using System.Windows.Forms; 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 Learning: Using a Background Thread
FormBorderStyle: FixedDialog Text: Road Traffic Monitoring MaximizeBox: False
SizeMode: AutoSize (Name): pbxRoadMap
Enabled: True Interval: 10 (Name): tmrTrafficMonitoring
using System; using System.Drawing; using System.Threading; using System.Windows.Forms; namespace RoadTrafficMonitoring14 { 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(); } } }
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 Learning: Blocking a Thread
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(); } } }
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 Learning: Creating a Threading Object
Text: Road Traffic Monitoring Size: 1608,1050 MaximizeBox: False
SizeMode: AutoSize (Name): pbxRoadMap Location: 0,0
using System.Drawing; namespace RoadTrafficMonitoring3 { public interface IVehicle { float X { get; set; } float Y { get; set; } Image Picture { get; set; } void Draw(Graphics graph); } }
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); } } }
Enabled: True Interval: 10 (Name): tmrTrafficMonitoring
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(); } } }
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-2021, FunctionX | Next |
|