Foundations of Threading

Introduction to Processes

The primary purpose of a computer is to provide electronic tools that allow a person to perform various types of operations, fast. This is made possible because the computer contains various types of applications, also called computer applications, or computer programs.

A computer application is made of one or various sub-applications that perform relatively small operations. The main purpose of a program is to do something, such as performing calculations in a spreadsheet or storing values in the computer memory. To make this possible, the operating system must be able to launch or execute a program. This involves millions (or billions) of operations performed and processed behind-the-scenes in the computer. For this reason, a computer application is called a process.

Introduction to Threads

As mentioned previously, a process or application is made of small sub-programs that take care of internal operations of a computer application. For example, in a graphics application, one sub-program can be in charge of arranging a series of bits (1s and 0s) to produce a clear or recognizable picture. Another sub-program can check the correctness of bits and retrieve their particular arrangement (of the bits) to display them on the monitor screen to produce a meaningful picture. Another sub-program can take care of keeping the arranged bits on the screen so the picture or graphics would not randomly vibrate, and so on. Another sub-program may be in charge of displaying an application on the screen, to the user. Those sub-programs are put (or work) together to produce the results sought by a user. Of course, these sub-programs work behind-the-scenes (and of course, the user doesn't care about what is happening). These small sub-applications are called threads.

For a process to perform its desired operation(s), the operating system must give it (allocate to it) the resources that are necessary. There are low-level resources that involve the hardware and high-level resources. Examples of low-level resources are the processor's ability to perform calculations, the processor's time used to perform a calculation or concentrate on some job a process needs, the processor's ability to consider that a certain job is more important (or requires priority) than (as compared to) another job, the motherboard's ability to carry (transport) information (data) from one side of the computer to another, a (hard, optical, USB, etc) drive's ability to read or write 1s and 0s, etc. Examples of high-level resources are the name of the application that is using the resources, the folder or the directory in which the application is installed, the permissions used to deal with the process, the ability to start or stop a process.

Obviously, this simplistic description would involve millions or billions of operations. A thread is something, anything, that needs and receives the operating system's attention to perform a particular operation. As a result, a process is in fact one or a series of threads that perform the necessary operations.

Single-Tasking Environment

In a single-tasking environment, one operation is performed. When that operation ends, the next operation starts and in fact, one operation must wait for the ongoing (or previous) operation to complete.

Multi-Tasking Environment

When computers were performing a limited number of operations and processors were simple, the operating system was solely in charge of dealing with threads. Nowadays, computers and processors have become complex and we have become demanding with them. In fact, a typical computer or user nowadays needs to do many things at the same time, in what we have designated as multitasking.

Modern processors are now equipped with many internal sub-processors, called cores, that can internally, but also independently, and simultaneously, perform many individual operations. As a result, you can create such individual operations for your application so that many individual operations can be performed at the same time instead of waiting for the ongoing (or the previous) operation to end.

Creating a Thread

Introduction

Multi-tasking programming consists of deciding how threads should manage your application. You start by creating one or more threads in your application and specifying what they should do. To support the ability to create threads, the .NET Framework provides a sealed class named Thread:

public sealed class Thread : CriticalFinalizerObject, 
			                 _Thread

A thread created in the .NET Framework, using the Thread class, is referred to as managed. This class is defined in the System.Threading namespace. The easiest way to create a thread is to declare a variable of this class. To let you initialize the variable, the Thread class is equipped with four constructors and there is no default constructor. The easiest constructor takes a delegate argument that specifies the method that will carry the operation of the thread. Its syntax is:

public Thread(ThreadStart start);

This constructor takes an argument based on a delegate named ThreadStart:

public delegate void ThreadStart();

This means that the method associated with the delegate takes no argument and returns nothing. This also means that you must have a method, and you have various options:

For the first three options, we created a static method. This was necessary because we were using the delegate in the static Main() method of the application. This is also necessary if you are creating the method in a static class or calling it from a static member in a non-static class. Normally, you can create the method as a regular one. In this case, you would have to declare a variable to access the method. Here is an example:

using System.Threading;
using static System.Console;

public class Entertainment
{
    public void WalkHome()
    {
        WriteLine("Everybody loves Candy.");
    }
}

public class Exercise
{
    public static int Main()
    {
        Entertainment balance = new Entertainment();

        ThreadStart tsMove = new ThreadStart(balance.WalkHome);

        return 0;
    }    
}

Creating Many Threads

As opposed to one, you may need to have various threads in your application. You can create each thread and use it as you see fit. Here are examples adapted from the earlier samples:

using System.Threading;
using static System.Console;

public class Entertainment
{
    public void WalkHome()
    {
        WriteLine("Everybody loves Candy.");
    }
}

public class Exercise
{
    public static int Main()
    {
        Entertainment balance = new Entertainment();

        ThreadStart tsMove = new ThreadStart(balance.WalkHome);

        Thread thPerson = new Thread(new ThreadStart(balance.WalkHome));

        Thread thAgent = new Thread(balance.WalkHome);

        Thread thEnergy = new Thread(() =>
        {
            WriteLine("Everybody loves Candy.");
        });

        return 0;
    }    
}

Primary Operations on a Thread

The Apartment Area of a Thread

When an application starts, or to manage a running program, the operating system allocates the necessary resources for it, such as the appropriate amount of memory. To manage the resources used by the threads of an application, the operating system also reserves an area, called an apartment, that those threads can consult for available resources. The characteristics of an apartment are defined in an enumeration named ApartmentState:

public enum ApartmentState { MTA, STA, Unknown };

In reality, to let you specify how an application (a process) will manage the apartment, the .NET Framework provides two attributes. Meanwhile, the members of the ApartmentState enumeration are:

To assist you with management or inquiries related to an apartment, the Thread class is equipped with methods named GetApartmentState() and SetApartmentState(). Their syntaxes are:

public ApartmentState GetApartmentState()
public void SetApartmentState(ApartmentState state);

As you can see, the combination of these two methods results in a (an undefined) property.

Starting a Thread

Obviously you are not the only one creating threads, or your application is not the only one running in the computer. As a result, when you create a thread, you are simply informing the operating system that you will use a thread, but you must specify when to execute that thread. To support this operation, the Thread class is equipped with an overloaded method named Start. The simplest version uses the following syntax:

public void Start();

As you can see, this is a simple method. It takes no argument and returns nothing. Here is an example of calling it:

using System;
using System.Threading;

public class Entertainment
{
    public void WalkHome()
    {
        Console.WriteLine("Everybody loves Candy.");
    }
}

public class Exercise
{
    [STAThread]
    public static int Main()
    {
        Entertainment balance = new Entertainment();

        Thread thPerson = new Thread(new ThreadStart(balance.WalkHome));

        thPerson.Start();

        return 0;
    }  
}

This would produce:

Everybody loves Candy.
Press any key to continue . . .

In the same way, if you had created many threads, you can execute (or start) each when you want.

Suspending a Thread

For some reason (and it will be up to you), you will not always want your thread to run, eternally. At some point, you may want to ask it to wait some time and resume later. This operation is referred to as pausing or suspending the activities of a thread. Once again, you have various options. To support the ability to pause a thread, the Thread class is equipped with an overloaded static method named Sleep. One of the versions uses the following syntax:

public static void Sleep(int millisecondsTimeout);

With this version, you pass the number of milliseconds during which the thread must wait before resuming its activities. The other version of the Thread.Sleep() method takes a TimeSpan argument. Its syntax is:

public static void Sleep(TimeSpan timeout);

Practical LearningPractical Learning: Suspending a Thread

  1. To start a new application, on the main menu of Microsoft Visual Studio, click File -> New -> Project...
  2. In the middle list, click Console App (.NET Framework)
  3. Change the Name to Chemistry7 and press Enter
  4. To create a class, on the main menu, click Project -> Add Class...
  5. In the middle list, mahe sure Class is selected.
    Change the Name of the file to Element
  6. Click Add
  7. To use a double-precision value, change the class as follows:
    namespace Chemistry7
    {
        public enum Phase
        {
            Gas,
            Liquid,
            Solid,
            Unknown
        }
    
        public class Element
        {
            public string Symbol       { get; set; } = "H";
            public string ElementName  { get; set; } = "Hydrogen";
            public int    AtomicNumber { get; set; } = 1;
            public float  AtomicWeight { get; set; } = 1.008F;
            public Phase  Phase        { get; set; } = Phase.Gas;
    
            public Element()
            {
            }
    
            public Element(int number)
            {
                AtomicNumber = number;
            }
    
            public Element(string symbol)
            {
                Symbol = symbol;
            }
    
            public Element(int number, string symbol, string name, float mass)
            {
                ElementName  = name;
                AtomicWeight = mass;
                AtomicNumber = number;
                Symbol       = symbol;
                Phase        = Phase.Unknown;
            }
    
            public Element(int number, string symbol, string name, float mass, Phase phase)
            {
                ElementName  = name;
                AtomicWeight = mass;
                Phase        = phase;
                AtomicNumber = number;
                Symbol       = symbol;
            }
        }
    }
  8. In the Solution Explorer, right-click Program and click Rename
  9. Type Chemistry to get Chemistry.cs and press Enter
  10. Read the text on the message box and click Yes
  11. Click the Chemistry.cs tab to access it
  12. Change the document as follows:
    using System.Threading;
    using static System.Console;
    using System.Collections.Generic;
    
    namespace Chemistry7
    {
        public class Exercise
        {
            private static void Describe(Element e)
            {
                Clear();
                WriteLine("Chemistry - " + e.ElementName);
                WriteLine("-------------------------------");
                WriteLine("Symbol:        " + e.Symbol);
                WriteLine("Element Name:  " + e.ElementName);
                WriteLine("Atomic Number: " + e.AtomicNumber);
                WriteLine("Atomic Weight: " + e.AtomicWeight);
                WriteLine("Phase:         " + e.Phase);
                WriteLine("================================");
            }
    
            public static int Main(string[] args)
            {
                List<Element> elements = new List<Element>();
    
                elements.Add(new Element(1, "H", "Hydrogen", 1.008f, Phase.Gas));
                elements.Add(new Element(2, "He", "Helium", 4.002602f, Phase.Gas));
                elements.Add(new Element(3, "Li", "Lithium", 6.94f, Phase.Solid));
                elements.Add(new Element(4, "Be", "Beryllium", 9.0121831f, Phase.Solid));
                elements.Add(new Element(5, "B", "Boron", 10.81f, Phase.Solid));
                elements.Add(new Element(number: 6, symbol: "C", name: "Carbon", mass: 12.011f, phase: Phase.Solid));
                elements.Add(new Element(7, "N", "Nitrogen", 14.007f, Phase.Gas));
                elements.Add(new Element(8, "O", "Oxygen", 15.999f, Phase.Gas));
                elements.Add(new Element(9, "F", "Fluorine", 15.999f, Phase.Gas));
                elements.Add(new Element("Ne") { AtomicNumber = 10, ElementName = "Neon", AtomicWeight = 20.1797f, Phase = Phase.Gas });
                elements.Add(new Element(11, "Na", "Sodium", 22.98976928f, Phase.Solid));
                elements.Add(new Element(12, "Mg", "Magnesium", 24.305f, Phase.Solid));
                elements.Add(new Element(13, "Al", "Aluminium", 26.9815385f, Phase.Solid));
                elements.Add(new Element() { ElementName = "Silicon", AtomicWeight = 28.085f, Symbol = "Si", AtomicNumber = 14, Phase = Phase.Solid });
                elements.Add(new Element() { ElementName = "Phosphorus", AtomicWeight = 30.973761998f, Symbol = "P", AtomicNumber = 15, Phase = Phase.Solid });
                elements.Add(new Element(16, "S", "Sulfur", 32.06f, Phase.Solid));
                elements.Add(new Element(17, "Cl", "Chlorine", 35.45f, Phase.Gas));
                elements.Add(new Element(18, "Ar", "Argon", 39.792f, Phase.Gas));
                elements.Add(new Element(19, "K", "Potassium", 39.0983f, Phase.Solid));
                elements.Add(new Element(20, "Ca", "Calcium", 40.078f, Phase.Solid));
                elements.Add(new Element(21, "Sc", "Scandium", 44.955908f, Phase.Solid));
                elements.Add(new Element(22, "Ti", "Titanium", 47.867f, Phase.Solid));
    
                WriteLine("Chemistry - Periodic Table");
                WriteLine("------------------------------------------------");
    
                foreach (Element elm in elements)
                {
                    Describe(elm);
                    Thread.Sleep(3500);
                }
    
                return 0;
            }
        }
    }
  13. To execute the application, on the main menu, click Debug -> Start Without Debugging:
    Chemistry - Hydrogen
    -------------------------------
    Symbol:        H
    Element Name:  Hydrogen
    Atomic Number: 1
    Atomic Weight: 1.008
    Phase:         Gas
    ================================
    Press any key to continue . . .
  14. Notice that each element displays after another
    Chemistry - Titanium
    -------------------------------
    Symbol:        Ti
    Element Name:  Titanium
    Atomic Number: 22
    Atomic Weight: 47.867
    Phase:         Solid
    ================================
    Press any key to continue . . .
  15. Press Enter to close the DOS window and return to your programming environment

A Thread in Infinite Timeout

If you had created many threads, when necessary, you can suspend those you want when you want. The let you suspend a thread indefinitely, the .NET Framework provides the very short static Timeout class that is equipped with only two constant-value fields as members. They are named Infinite and InifiniteTimeSpan:

public const int Infinite;
public static readonly TimeSpan InfiniteTimeSpan;

Based on this, to indefinitely put a thread to sleep, pass Timeout.Infinite as argument to the Thread.Sleep() method.

Terminating a Thread

As mentioned already, a thread is part of an application. This means that when an application closes, the operating system (OS) terminates all the related or associated threads. This also means that normally, the OS takes (and is equipped to take) care of threads. Still, in some cases (and only you will decide why), you will need to terminate a thread, even if its application is still running.

As one way to support the ability to terminate a thread, the Thread class is equipped with an overloaded method named Abort. Actually, the Thread.Abort() method neither suspends nor terminates a thread. It only lets the operating system (OS) to know that the thread should be terminated. The operating system is left with deciding what to do (including when and how).

One of the versions of the Thread.Abort() method takes no argument. Its syntax is:

public void Abort();

As you can see, this method is simply asked to terminate its thread (remember that this method doesn't really terminate a thread). In the same way, you can terminate as many threads as you want in your program.

The Thread.Abort() method hardly does any processing. In fact, this method relies on you (and on the OS) to take care of any problem. Thread abortion (or termination) is neither guaranteed nor predictable. One way you can assist, or inform, the OS about the operation (of terminating your thread) is to provide some information about it. To let you provide such information, the Thread class provides another version of the Abort() method. Its syntax is:

public void Abort(object stateInfo);

This version takes an argument that can hold and provide more details about the operation.

Processes

Introduction

As mentioned in our introduction to threading, an application that runs in a computer is also called a process. To support processes, the .NET Framework provides a class named Process that is defined in the System.Diagnostics namespace.

Creating a Process

The main library of the Microsoft Windows operating systems, named the Win32 library, is equipped with various types of functions to create and manage a process. In the .NET Framework, to deal with a process, you can first declare a variable of type Process.

Starting a Process

Launching an application is the same as starting a process. In Microsoft Windows, one way you can do this is to click the Start button in the Taskbar, locate the desired application, and click it. You can also double-click the name of an executable in a file utility such as Windows Explorer or File Explorer. There are many other ways. In all cases, you use the name of an executable to launch the application.

The Details of a Process

Introduction

To give you more information about a process, the .NET Framework provides a class named ProcessStartInfo, also defined in the System.Diagnostics namespace. To support the details of a process, the Process class is equipped with a property named Start​Info:

public System.Diagnostics.ProcessStartInfo StartInfo { get; set; }

Of course before accessing this property, first declare a Process variable. From that variable, you can access its Start​Info property. From that property, you can access the members of the ProcessStartInfo class.

Starting a Process

After providing the information of a process, you can launch it. To support this, the Process class provides another version of its Start() method whose syntax is:

public bool Start();

Call this method after defining a ProcessStartInfo object and setting it to a Process.StartInfo property.

The File to Open

Some applications can do only one thing, for example, a game application, such as an elecronic pinball, can only open its game and no other type of file. Some applications are meant to create and open their specific type of file but can be asked to open other types of files. For example, a spreadsheet application such as Microsoft Excel is made only to open a workbook, but with the effects of plug-ins (or component object embedding or ActiveX), they can open or host various types of files such as pictures. Some other applications are made to open various types of files. For example, an Internet browser can open webpages, PDFs, pictures, XML files, etc.

Consequently, when you launch some applications, some applications don't specify and may not predict what you want to do. For example, when you launch Microsoft Word, maybe you want to perform some type of graphic design or you want it to host a Microsoft Excel spreadsheet to perform some calculations before saving the document in PDF. Overall, the Process class wants you to specify a file you want to open. As one way to support this, the class is equipped with an overloaded method named Start. One of the versions of this method uses the following syntax:

public static System.Diagnostics.Process Start(string fileName);

This version takes the name or path of a file as argument. The name may or may not have an extension. When this version of the method is called:

As an alternative to the above Process constructor, the Process​Start​Info class is equipped with a constructor that takes a file name as argument. Its syntax is:

public ProcessStartInfo(string fileName);

To use this constructor, declare a variable and pass the file name or path as argument, then assign the variable to the Process.StartInfo property. Here is an example:

using static System.Console;
using System.Diagnostics;

public class Exercise
{
    public static int Main(string[] args)
    {
        ProcessStartInfo psi = new ProcessStartInfo(@"C:\Exercise\Long Time Ago.jpg");
        Process proc = new Process();
        proc.StartInfo = psi;

        proc.Start();
        
        return 0;
    }
}

As alternative, the Process​Start​Info class is equipped with a read-write property named FileName:

public string FileName { get; set; }

To open a file, assign its name or its name and path to this property, then call the Process.Start() method. Here is an example:

using static System.Console;
using System.Diagnostics;

public class Accountability
{
    public static int Main(string[] args)
    {
        ProcessStartInfo psi = new ProcessStartInfo();
        Process proc = new Process();

        psi.FileName = @"C:\Bethesda Car Rental\Employees Time Shees.xls";

        proc.StartInfo = psi;

        proc.Start();
        
        return 0;
    }
}

Exiting/Stopping a Process

The classic way to close an application in Microsoft Windows is to click the Close button on the top-right corner of a window. Normally, any regular application you create in C# is self-terminating. This is done when the compiler reaches the closing curcly bracket of the Main() method. Still there are time you want to control when and how your application stops, even if it hasn't gotten the regular end-point. To do this programmatically, you have various options.

To let you close an application at any time, the static Environment class is equipped with a method named Exit. Its a syntax is:

public static void Exit (int exitCode);

Call this method to end the current process. Here is an example:

using System;
using System.Collections.Generic;

public class Employee
{
    public long EmployeeNumber { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public double HourlySalary { get; set; }

    public Employee(long number = 0, string fName = "John",
                    string lName = "Doe", double salary = 12.05D)
    {
        EmployeeNumber = number;
        FirstName = fName;
        LastName = lName;
        HourlySalary = salary;
    }

    public override string ToString()
    {
        base.ToString();

        return string.Format("================================\n" +
                             "Employee Record\n" +
                             "--------------------------------\n" +
                             "Employee #:    {0}\nFirst Name:    {1}\n" +
                             "Last Name:     {2}\nHourly Salary: {3}",
                             EmployeeNumber, FirstName,
                             LastName, HourlySalary);
    }
}

// A class named Manager derived from Employee
public class Manager : Employee
{
    public string Title { get; set; }

    public override string ToString()
    {
        base.ToString();

        return string.Format("================================\n" +
                             "Manager Information\n" +
                             "--------------------------------\n" +
                             "Employee #:    {0}\nFirst Name:    {1}\n" +
                             "Last Name:     {2}\nHourly Salary: {3}\n" +
                             "Title:         {4}\n", EmployeeNumber, FirstName,
                             LastName, HourlySalary, Title);
    }
}

public class Exercise
{
    private static List<Employee> GetEmployees()
    {
        List<Employee> staff = new List<Employee>();
        Employee empl = null;

        empl = new Employee();
        empl.EmployeeNumber = 253055;
        empl.FirstName = "Joseph";
        empl.LastName = "Denison";
        empl.HourlySalary = 12.85;
        staff.Add(empl);

        empl = new Employee();
        empl.EmployeeNumber = 204085;
        empl.FirstName = "Raymond";
        empl.LastName = "Ramirez";
        empl.HourlySalary = 9.95;
        staff.Add(empl);

        empl = new Employee();
        empl.EmployeeNumber = 970044;
        empl.FirstName = "James";
        empl.LastName = "Macies";
        empl.HourlySalary = 14.25;
        staff.Add(empl);

        return staff;
    }

    private static List<Manager> GetManagers()
    {
        List<Manager> leaders = new List<Manager>();
        Manager mgr = null;

        mgr = new Manager();
        mgr.EmployeeNumber = 249730;
        mgr.FirstName = "Alex";
        mgr.LastName = "Joyner";
        mgr.HourlySalary = 32.85;
        mgr.Title = "Regional Manager";
        leaders.Add(mgr);

        mgr = new Manager();
        mgr.EmployeeNumber = 579047;
        mgr.FirstName = "Robert";
        mgr.LastName = "Farley";
        mgr.HourlySalary = 28.55;
        mgr.Title = "Shift Supervisor";
        leaders.Add(mgr);

        return leaders;
    }

    public static int Main()
    {
        
        IEnumerable<Manager> managers = GetManagers();
        IEnumerable<Employee> employees = managers;

        foreach (Employee empl in employees)
            Console.WriteLine(empl);

        Environment.Exit(0);

        foreach (Manager man in managers)
            Console.WriteLine(man);

        Console.WriteLine("================================");

        return 0;
    }
}

In the same way, call this method anywhere you want to close your aplication, such as in response to a closing statement.

Practical LearningPractical Learning: Ending the Lesson


Previous Copyright © 2016-2019, FunctionX Next