A Task

Introduction

At this time, we know that, to get an application working in a computer, you create one or more threads. Each thread is in charge of performing a particular operation. Such an operation is called a task.

Practical LearningPractical Learning: Introducing Tasks

  1. Start Microsoft Visual Studio and create a WinForms App application named TrafficTicketsManagement1
  2. In the Solution Explorer, right-click Form1.cs and click Rename
  3. Type TicketManagement (to get TicketManagement.cs)
  4. Press Enter twice to accept the name and display the form
  5. In the Common Controls section of the Toolbox, click the Button and click the form
  6. In the Properties windows, change the following characteristics of the button
    (Name): btnOverview
    Text:   Overview

Synchronous Operations

Imagine you have one line of trains on a single set of rails between two locations. In one location, you assemble and produce some objects, such as vehicles, that you must ship to the other location where they are sold at a car dealership:

Synchronous and Asynchronous Operations

Since you have only one line of trains, only one train can travel at a time to deliver the products from the starting point to the other. Only one train at a time can start from the manufacturer. If many trains must travel from the manufacturer, only one must start. The next train would start only after the previous one has left. In this type of scenario, one operation (a train leaving the station) must complete before the next (similar) operation starts (before the next train leaves the station). Your business operating with one line of trains would be called a single-threaded application. This is referred to as a synchronous operation.

Introduction to Asynchronous Operations

With only one line of trains, you can make only one delivery. Each train must wait for the one ahead to leave. Because your employees must keep track of all the movements of all trains and avoid accidents, one of the things they must do is to always block many of the operations in order to conduct one particular operation. In fact, if something bad, such as an accident, happens to the running train, everything must stop until the problem is addressed or fixed. This can slow your business and make merchandise delivery take too much time.

Imagine your business starts growing. One, and probably the best, thing you can do to deliver more products (and consequently work faster) is to have more than one line of trains. That way, different trains carrying merchandise can leave the manufacturer at the same time. In fact, you can have as many trains leaving the manufacturer as train lines are available:

Synchronous and Asynchronous Operations

In fact also, different trains can leave at different times:

Synchronous and Asynchronous Operations

This means that the departing and/or the activities of one train don’t depend on another train. In fact, even if one train is having problems, such as an accident, on one line, the other trains would still operate. You can also distribute work. For example, if you juge it necessary, you can:

When various operations can (must) be performed at the same time without one operation interferring with another, the operations are said to run asynchronously.

Introduction to Task Procedures

Overview

Continuing with our analogy of a car manufacturer who needs to ship many and various (types of) cars, some operations on a computer use a lot of resources and some operations take a long time to complete. One of the consequences is that one operation could block resources or processing time that other operations may need. As mentioned to the above car manufacturer analogy, one of the ways you can make operations run faster in your project is to run many operations at the same time. This concept certainly also applies when different applications in the same computer must share resources (the same file, the same media player, etc), when various users using different connected computers (various computers connected to each other or computers connecting to a server) must access the same application (database, email, etc), different browsers or various tabs accessing a particular webpage from one webserver at the same time, etc. To eliminate or reduce the likelihood of operations interfering with each other, we saw that you create threads and run each thread on its own area of work. This means that the threads, or various operations, are running asynchronously.

Introduction to Tasks

To support the concept of various operations running asynchronously, the .NET Framework provides the concepts of tasks.A task is an operation that runs in a manner that it would not interfere with other operations (that is, other tasks) on the same computer. This means that a task, or an action, is an operation that runs asynchronously. Compared to functions (or methods of a class), the .NET Framework provides two categories of tasks: those that don't produce a result (also called procedures) and those that return a value or an object (also called functions).

To support tasks, that is, to allow you to create a (one or more) task(s), the .NET Framework provides a class named Task. This class is defined in the System.Threading.Tasks namespace. If you create a Console or a Windows Forms application in Microsoft Visual Studio, the application automatically supports tasks, so you don't have to include that namespace to your document(s).

Practical LearningPractical Learning: Introducing the Threaded Tasks Namespace

  1. On the form, double-click the Overview button
  2. Change the document as follows:
    namespace TrafficTicketsManagement1
    {
        public partial class TicketManagement : Form
        {
            public TicketManagement()
            {
                InitializeComponent();
            }
    
            void Introduce()
            {
            }
    
            private void btnOverview_Click(object sender, EventArgs e)
            {
            }
        }
    }

Creating a Task

The Task class uses many constructors for different goals. The simplest constructor uses the following syntax:

public Task(Action action);

This version of the Task constructor takes a void method as argument. You can pass the name of the method as argument. Here is an example:

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

        void TurkishProverb()
        {
            MessageBox.Show("A tree won't fall with a single blow.", "Turkish Proverbs");
        }

        private void btnTask_Click(object sender, EventArgs e)
        {
            Task wisdom = new Task(TurkishProverb);
        }
    }
}

You can also define the method directly where it is needed. Here is an example:

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

        private void btnTask_Click(object sender, EventArgs e)
        {
            Task wisdom = new Task(() =>
            {
                MessageBox.Show("A tree won't fall with a single blow.", "Turkish Proverbs");
            });
        }
    }
}

Practical LearningPractical Learning: Creating a Task

Starting a Task

After creating a task, you can launch its operation. To support this, the Task class is equipped with a method named Start. It is overloaded with two versions. The simplest version uses the following syntax:

public void Start();

Simply call this method to perform the task. Here is an example:

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

        void TurkishProverb()
        {
            MessageBox.Show("A tree won't fall with a single blow.", "Turkish Proverbs");
        }

        private void btnTask_Click(object sender, EventArgs e)
        {
            Task wisdom = new Task(TurkishProverb);

            wisdom.Start();
        }
    }
}

This would produce:

Tasks Fundamentals

Practical LearningPractical Learning: Starting a Task

  1. Change the document as follows:
    namespace TrafficTicketsManagement1
    {
        public partial class TicketManagement : Form
        {
            public TicketManagement()
            {
                InitializeComponent();
            }
    
            void Introduce()
            {
                MessageBox.Show("This application allows the police department (and the county " +
                                "traffic services) to evaluate a ticket amount when a vehicle " +
                                "driver has violated a road-traffic law. There are various types " +
                                "of infractions and different laws (the laws are also different " +
                                "from county to county)." + 
                                Environment.NewLine + Environment.NewLine +
                                "Most of the time, the amount of a ticket may depend on the " +
                                "type of infraction, the level (or severity) of a violation, " +
                                "and some factors that may be left to a police officer's " + 
                                "discretion or to the appreciation of the government employee " +
                                "(and sometimes a judge) who is overseeing the matter." + 
                                Environment.NewLine + Environment.NewLine +
                                "This application is used to (electronically) evaluate the " +
                                "amount applied to a road-traffic ticket based on the type " + 
                                "of violation (this application is for entertainment " + 
                                "purpose only and is not based on anything real).",
                                "Traffic Ticket Management");
            }
    
            private void btnOverview_Click(object sender, EventArgs e)
            {
                Task overview = new Task(Introduce);
    
                overview.Start();
            }
        }
    }
  2. To execute, on the main menu, click Debug -> Start Without Debugging
  3. Click the button on the form:

    Starting a Task

  4. Click OK on the message box
  5. Close the form and return to your programming environment
  6. Return to the form and complete its design as follows:

    Introduction to Tasks

    Control (Name): Text TextAlign
    Label   Speed Limit:  
    TextBox txtSpeedLimit   Right
    Label   Driver Speed:  
    TextBox txtDriverSpeed   Right
    Button btnSpeedTicketAmount Ticket Amount  
    TextBox txtSpeedTicketAmount   Right
    Button btnClose Close  
  7. On the form, double-click the Ticket Amount button
  8. Change the document as follows:
    namespace TrafficTicketsManagement1
    {
        public partial class TicketManagement : Form
        {
            public TicketManagement()
            {
                InitializeComponent();
            }
    
            void Introduce()
            {
                . . .
            }
    
            private void btnOverview_Click(object sender, EventArgs e)
            {
                . . .
            }
    
            (int limit, int driving) speed;
    
            /* A function to set the amount of a ticket for a vehicle driving 
             * higher than the speed limit. The amount of the ticket depends 
             * on how much more the car was moving above the speed limit. */
            int SetTicketAmountSpeed()
            {
                int speedDifference = speed.driving - speed.limit;
    
                if (speedDifference < 10)
                    return 0;
                else if (speedDifference < 20)
                    return 75;
                else if (speedDifference < 35)
                    return 100;
                else if (speedDifference < 50)
                    return 150;
                else
                    return 225;
            }
    
            private void btnSpeedTicketAmount_Click(object sender, EventArgs e)
            {
                
            }
        }
    }

Running a Task

Consider the following code:

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

        void SocialScience()
        {
            MessageBox.Show("When you commit a suicide, the worse thing that can " +
                            "happen to you is that you don't die.", "Social Science");
        }

        private void btnTask_Click(object sender, EventArgs e)
        {
            Task warning = new Task(SocialScience);

            warning.Start();
        }
    }
}

In the above code, we first defined a task before starting it. As an alternative, the Task class provides an overloaded static method named Run. It is available in various versions for different goals. One of the versions uses the following syntax:

public static Task Run (Action action);

This method (or this version) takes a void function or method as argument. When the Task.Run() function is called, it automatically starts the task passed to it. Here is an example:

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

        void SocialScience()
        {
            MessageBox.Show("When you commit a suicide, the worse thing that can " +
                            "happen to you is that you don't die.", "Social Science");
        }

        private void btnTask_Click(object sender, EventArgs e)
        {
            Task.Run(SocialScience);
        }
    }
}

This would produce:

Running a Task

Remember that, as an alternative, if you are planning to call a function or method only once, you can nest that function in the function where it will be called. Here is an example:

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

        private void btnTask_Click(object sender, EventArgs e)
        {
            Task.Run(SocialScience);
            
            void SocialScience()
            {
                MessageBox.Show("When you commit a suicide, the worse thing " +
                                "that can happen to you is that you don't die.",
                                "Social Science");
            }
        }
    }
}

When calling the Task.Run() method, you don't have to first create a function or method for the task. Instead, you can create a lambda expression. Of course, you have many options. You can first define a local Action expression, then pass its name as argument to the Task.Run() function. Here is an example:

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

        private void btnTask_Click(object sender, EventArgs e)
        {
            Action say = () =>
            {
                MessageBox.Show("When you commit a suicide, the worse thing " +
                                "that can happen to you is that you don't die.",
                                "Social Science");
            };
            
            // You can do something else here ...
            
            Task.Run(say);
        }
    }
}

Alternatively, you can create a lambda expression in the parentheses of the method. Here is an example:

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

        private void btnTask_Click(object sender, EventArgs e)
        {
            Task.Run(() =>
            {
                MessageBox.Show("When you commit a suicide, the worse thing " +
                                "that can happen to you is that you don't die.",
                                "Social Science");
            });
        }
    }
}

A Thread Pool as a Collection of Tasks

Remember that many tasks run in a computer. In previous lessons, we were introduced to thread pools. A thread pool is a collection of threads or takes that must be performed. As a matter of facts, to keep track of the tasks that must be perforemed in an application, you use a ThreadPool object as a collection of threads. The tasks are added in the collection by order.

When you call the static Task.Run() function (any of its versions), it returns a Task object. That object represents the task that was created and adds it in the collection of tasks that must be executed, which is a ThreadPool collection.

If you want, when calling the Task.Run() function, you can get its returned Task object. To do that, you can declare a Task variable and assign the call of Task.Run() to it. Here is an example:

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

        private void btnTask_Click(object sender, EventArgs e)
        {
            Task done = Task.Run(SocialScience);
            
            void SocialScience()
            {
            }
        }
    }
}

If you declare and initialize the variable like that, the task would still first execute, and the function would produce a Task object.. After that, you can use the variable any appropriate way you want

A Procedural Task as a Type

A Task object can be used as a type. This means that you can create a function or method that returns a task. Here are two examples:

namespace GeneralWork
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }
        
        // A function that returns a task
        Task SayGoodbye()
        {
            Task wisdom = new Task(() =>
            {
                MessageBox.Show("Thank you for your visit.", "Dental Office");
            });

            return wisdom;
        }

        // Another function that returns a task
        Task Repeat()
        {
            return new Task(() =>
            {
                MessageBox.Show("Make sure you make the next appointment", "Appointments");
            });
        }

        private void btnTask_Click(object sender, EventArgs e)
        {
            Task something = SayGoodbye();

            Task one = Repeat();
        }
    }
}

On the other hand, you can create a function or method that takes a Task object as argument. Here is an example:

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

        Task Quote()
        {
            Task wisdom = new Task(() =>
            {
                MessageBox.Show("Nihilism is a natural consequence of a culture " +
                                "(or civilization) ruled and regulated by categories " +
                                "that mask manipulation, mastery and domination of " +
                                "peoples and nature. - Cornell West", "Social Science");
            });

            return wisdom;
        }

        Task Repeat()
        {
            return new Task(() =>
            {
                MessageBox.Show("You only pass through this life once, you " +
                                "don't come back for an encore.", "Social Science");
            });
        }

        void HandleIt(Task working)
        {
            working.Start();
        }

        private void btnTask_Click(object sender, EventArgs e)
        {
            Task one = Repeat();

            HandleIt(one);
            HandleIt(Quote());
        }
    }
}

This would produce:

A Procedural Task as a Type

A Procedural Task as a Type

Introduction to Functional Tasks

A Functional Task

As seen with functions in previous lessons, some operations must produce a value. That's the case for methods that return a value or an object. To support tasks that produce a value, the .NET Framework provides a generic Task<> class. In fact, this class inherits from the non-generic Task class:

public class Task<TResult> : Task

Creating a Functional Task

To create a task for a function, declare a variable of the Task<> class. As seen when studying generics, you must specify the parameter type. In this case, this is the type returned by the function. The parameter type can be a primitive (number-based, character, Boolean) or a string. It can also be a class type.

To let you initialize a task, the Task<> class is equipped with various constructors. The simplest constructor uses the following syntax:

public Task(Func<TResult> function);

This constructor takes a function or a method as argument. The function doesn't take any argument but it must return any value you want. You can first define a function or method and pass its name as the argument to the above constructor. Here is an example:

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

        (int limit, int driving) speed;

        float SetTicketAmountSpeed()
        {
            int speedDifference = speed.driving - speed.limit;

            if (speedDifference < 10f)
                return 0;
            else if (speedDifference < 20f)
                return 75f;
            else if (speedDifference < 35f)
                return 100f;
            else if (speedDifference < 50f)
                return 150f;
            else
                return 225f;
        }

        private void btnTask_Click(object sender, EventArgs e)
        {
            Task<float> price = new Task<float>(SetTicketAmountSpeed);
        }
    }
}

Alternatively, you can define asystem. function as a Func<> object and then pass its name to the constructor. Here is an example:

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

        private void btnTask_Click(object sender, EventArgs e)
        {
            (int limit, int driving) speed = (0, 0);

            Func<float> SetTicketAmountSpeed = () =>
            {
                int speedDifference = speed.driving - speed.limit;
                
                if (speedDifference < 10f)
                    return 0;
                else if (speedDifference < 20f)
                    return 75f;
                else if (speedDifference < 35f)
                    return 100f;
                else if (speedDifference < 50f)
                    return 150f;
                else
                    return 225f;
            };

            Task<float> price = new Task<float>(SetTicketAmountSpeed);
        }
    }
}

One more option is to define the function as a lambda expression in the parentheses of the constructor.

Practical LearningPractical Learning: Creating a Task

Introduction to Starting a Functional Task

After creating a task, there are many ways you can launch it. As one way to do this, and as seen for the Task class, the Task<> class inherits the Start() method from its parent.

Practical LearningPractical Learning: Starting a Functional Task

The Value of a Functional Task

Remember that, unlike the Task object, a Task<int> produces a value. To let you get that value, the Task<int> class is equipped with a property named Result:

public TResult Result { get; }

Practical LearningPractical Learning: Getting the Result of a Task

  1. Change the Click event of the Ticket Amount button as follows:
    namespace TrafficTicketsManagement12
    {
        public partial class TicketManagement : Form
        {
            public TicketManagement()
            {
                InitializeComponent();
            }
    
            void Introduce()
            {
                . . .
            }
    
            private void btnOverview_Click(object sender, EventArgs e)
            {
                Task overview = new Task(Introduce);
    
                overview.Start();
            }
    
            (int limit, int driving) speed;
    
            /* A function to set the amount of a ticket for a vehicle driving 
             * higher than the speed limit. The amount of the ticket depends 
             * on how much more the car was moving above the speed limit. */
            int SetTicketAmountSpeed()
            {
                int speedDifference = speed.driving - speed.limit;
    
                if (speedDifference < 10)
                    return 0;
                else if (speedDifference < 20)
                    return 75;
                else if (speedDifference < 35)
                    return 100;
                else if (speedDifference < 50)
                    return 150;
    else
    return 225; } private void btnSpeedTicketAmount_Click(object sender, EventArgs e) { speed.limit = int.Parse(txtSpeedLimit.Text); speed.driving = int.Parse(txtDriverSpeed.Text); Task<int> ticketSpeed = new Task<int>(SetTicketAmountSpeed); ticketSpeed.Start(); txtSpeedTicketAmount.Text = ticketSpeed.Result.ToString(); } } }
  2. To execute the application, on the main menu, click Debug -> Start Without Debugging:

    Getting the Result of a Task

  3. In the Speed Limt text box, type 35
  4. Click the Driver Speed text box and type 45

    Getting the Result of a Task

  5. Click the Ticket Amout button:

    Getting the Result of a Task

  6. Close the form and return to your programming environment
  7. Return to the form and complete its design as follows:

    Introduction to Tasks

    Control (Name): Text Other Properties
    Label   Police Stop Reason:  
    ComboBox cbxPoliceStops   Items:
    Other
    Tail Light
    Reckless
    Break Light
    Under Influence
    TextBox txtPoliceTicketAmount   TextAlign: Right
  8. On the form, double-click the Police Stop Reason combo box
  9. Return to the form and double-click the Close button

Running a Functional Task<>

The Task<> class inherits the Run() method from its parent. This gives you various ways to immediately (create and) start a task. When it comes to a generic task, if you want, you can specify the parameter type on the Task object and/or the Run() method.

Practical LearningPractical Learning: Running a Task

  1. Change the events as follows:
    namespace TrafficTicketsManagement1
    {
        public partial class TicketManagement : Form
        {
            public TicketManagement()
            {
                InitializeComponent();
            }
    
            void Introduce()
            {
                MessageBox.Show("This application allows the police department (and the county " +
                                "traffic services) to evaluate a ticket amount when a vehicle " +
                                "driver has violated a road-traffic law. There are various types " +
                                "of infractions and different laws (the laws are also different " +
                                "from county to county)." + 
                                Environment.NewLine + Environment.NewLine +
                                "Most of the time, the amount of a ticket may depend on the " +
                                "type of infraction, the level (or severity) of a violation, " +
                                "and some factors that may be left to a police officer's " + 
                                "discretion or to the appreciation of the government employee " +
                                "(and sometimes a judge) who is overseeing the matter." + 
                                Environment.NewLine + Environment.NewLine +
                                "This application is used to (electronically) evaluate the " +
                                "amount applied to a road-traffic ticket based on the type " + 
                                "of violation (this application is for entertainment " + 
                                "purpose only and is not based on anything real).",
                                "Traffic Ticket Management");
            }
    
            private void btnOverview_Click(object sender, EventArgs e)
            {
                Task overview = new Task(Introduce);
    
                overview.Start();
            }
    
            (int limit, int driving) speed;
    
            /* A function to set the amount of a ticket for a vehicle driving 
             * higher than the speed limit. The amount of the ticket depends 
             * on how much more the car was moving above the speed limit. */
            int SetTicketAmountSpeed()
            {
                int speedDifference = speed.driving - speed.limit;
    
                if (speedDifference < 10)
                    return 0;
                else if (speedDifference < 20)
                    return 75;
                else if (speedDifference < 35)
                    return 100;
                else if (speedDifference < 50)
                    return 150;
                else
                    return 225;
            }
    
            private void btnSpeedTicketAmount_Click(object sender, EventArgs e)
            {
                speed.limit = int.Parse(txtSpeedLimit.Text);
                speed.driving = int.Parse(txtDriverSpeed.Text);
    
                Task<int> ticketSpeed = new Task<int>(SetTicketAmountSpeed);
    
                ticketSpeed.Start();
    
                txtSpeedTicketAmount.Text = ticketSpeed.Result.ToString();
            }
    
            private void cbxPoliceStops_SelectedIndexChanged(object sender, EventArgs e)
            {
                /* This local function presents some of the most common reasons a police officer 
                 * would stop a car. Some of the reasons are: 
                 * * TailLight: Broken tail light 
                 * * BreakLight: Either one of the break lights is broken 
                 * *             or the driver failed to break, for any reason 
                 * * Reckless: Reckless driving - maybe the driver is distracted or having too much fun 
                 * * UnderInfluence: Driving under the influence of something (alcohol, drugs, etc 
                 * * Other: Either it is not one of the above listed reason, 
                 * *        or we are not sure why the police stopped the car
                 * The amount of the ticket is evaluated based on those reasons.
                 * */
                Func<int> evaluatePoliceStop = new Func<int>(() =>
                {
                    int amount = 0;
    
                    switch (cbxPoliceStops.Text)
                    {
                        case "Tail Light":
                            amount = 40;
                            break;
                        case "Break Light":
                            amount = 60;
                            break;
                        case "Reckless":
                            amount = 160;
                            break;
                        case "Under Influence":
                            amount = 300;
                            break;
                        case "Other":
                            amount = 25;
                            break;
                        default:
                            amount = 0;
                            break;
                    }
    
                    return amount;
                });
    
                Task<int> processPoliceIssue = Task<int>.Run<int>(evaluatePoliceStop);
    
                txtPoliceTicketAmount.Text = processPoliceIssue.Result.ToString();
            }
    
            private void btnClose_Click(object sender, EventArgs e)
            {
                Close();
            }
        }
    }
  2. To execute the project, on the main menu, click Debug -> Start Without Debugging

    Running a Functional Task

  3. In the Police Stop Reason combo box, select Reckless

    Running a Functional Task

  4. Close the form and return to your programming environment
  5. Close your programming environment

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