Page 1 of 1

Very Basic Introduction to Tasks

#1 CodingSup3rnatur@l-360  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 992
  • View blog
  • Posts: 972
  • Joined: 30-September 10

Posted 23 January 2011 - 03:56 PM

*
POPULAR

Basic Introduction to Tasks

This tutorial aims to serve as the most basic of introductions to parallel programming and, specifically, the Task class. To gain access to the Task class, you need to add this using statement to your programs:

using System.Threading.Tasks;


Now, parallel programming is a sub topic of multithreading. When we execute things on multiple threads, it is by no means guaranteed that the threads are separated across multiple processors. To the contrary, running code in parallel means the work is spread across multiple processors to maximise computational speed. This is one of the key benefits of tasks, as they are tuned to execute computationally intensive work using multiple cores.

Why should I use Tasks over standard multithreading?

1) It is the way Microsoft is moving with regards to asynchrony and multithreading. For example, the all new ‘async’ and ‘await’ keywords use Tasks extensively.

2) Tasks have been optimised to make use of the CLR thread pool, and so do not have the overhead associated with creating a dedicated thread using the Thread class. I think I am also correct in saying that the tweaking Microsoft have done on Tasks makes them more efficient than interacting with the thread pool via ThreadPool.QueueUserWorkItem().

3) Tasks implement local work queues. This optimization allows you to efficiently create many quickly executing child tasks without incurring the contention overhead that would otherwise arise with a single work queue.

4) You can wait on the completion of tasks without the use of signalling constructs (Monitor class etc).

5) You can chain tasks together to execute one after the other.

6) Is it just me, or is it just more logical to separate units of computationally intensive work into separate tasks, rather than working directly with threads? This is partly as a result of the higher level of abstraction that tasks provide.


I am sure there are many more reasons to use tasks that I have missed…

It amazes me how few people actually even know about tasks, let alone think about using them. Hopefully this tutorial will give you a starting point.

A basic example

In this example, I am going to simulate a computationally intensive operation in a WinForms application. Now, I am sure many of you realise that executing code that may take a while to return synchronously causes an unresponsive user interface. The user is stranded until the code returns.

I am sure many of you in this situation will turn to the Thread class, BackgroundWorker class or something similar in order to execute the long running code on a separate thread to ensure the UI remains responsive. I am going to show you how you could do this with the Task class…

Firstly, open up a new WinForms application and call it DICTasks.

Recreate the following form (see below) by dragging the relevant components from the toolbox. The large white box is just a multiline text box (set the multiline property to true). The two components at the bottom are numeric up downs. It doesn't matter about getting it perfect (God knows, mine is far from perfect!), as that's not the idea behind this tutorial.

Attached Image

Give the components the following names:

Textbox – TxtNoOfPrimes

Button – BtnCalculate

Left hand side numeric up down – NudLower

Right hand side numeric up down – NudUpper

Don’t worry about naming the labels.


At this point, I should probably explain that this application will be used to count prime numbers. The user enters a range using the numeric up downs, and when they click ‘Calculate’, the number of prime numbers within that range are found and displayed in the text box. The idea is that if the user enters a large range, it will take some time to count all the prime numbers, and hence the UI will become unresponsive until the count completes, unless we take measures to prevent it…


Now you have the UI built, right click on the form and click ‘View Code’. The code should look something like this:

using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace DICTasks
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    }
}




Add this line to the constructor (Form1()):

this.NudUpper.Maximum = this.NudLower.Maximum = int.MaxValue;



That will allow the user some higher numbers in the numeric up downs.


Now, I have made two methods that will allow the counting of prime numbers in the range. Add these to the Form1 class:

private IEnumerable<int> getPrimesInRange(int inclusiveStartValue, int exclusiveEndValue)
{
    System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(2000));
    return getAllPrimes(Enumerable.Range(inclusiveStartValue, (exclusiveEndValue - inclusiveStartValue)));
}

private IEnumerable<int> getAllPrimes(IEnumerable<int> numbers)
{
    return from n in numbers where (!(n <= 1) && Enumerable.Range(2, (int)Math.Sqrt(n == 2 ? 0 : n)).All(i => n % i > 0)) select n;
}




Notice how I have added the call to Thread.Sleep(), just to really drag on the counting process for a reasonable period of time, in order to simulate a long running operation.

Now, double click on the Calculate button in the designer, and this method should have appeared in the code:


  
private void BtnCalculate_Click(object sender, EventArgs e)
{

}



Add this line to that method:

this.TxtNoOfPrimes.Text += this.getPrimesInRange(int.Parse(this.NudLower.Value.ToString()), int.Parse(this.NudUpper.Value.ToString())).Count().ToString() + Environment.NewLine;



This uses the values in the numeric up downs as the range, and uses the getPrimesInRange() method to return an IEnumerable of all the primes in the range, I then count all entries in the IEnumerable, and add that to the text box. The ‘Environment.NewLine’ bit just makes sure that each count value is assigned it’s own line in the text box.

Now, just to ensure the user cannot enter a higher value in the lower numeric up down, than in the upper numeric up down, we shall handle the validating event of the lower numeric up down.

To do this, single click on the numeric up down called NudLower, go into the properties panel and then into the events pallet (click on the yellow lightening bolt at the top of the properties panel). Then, find the event called ‘Validating’ and double click on it.

In the code, you should now have this method:

private void NudLower_Validating(object sender, CancelEventArgs e)
{

}



Add this line to that method:

if (this.NudLower.Value > this.NudUpper.Value) { this.NudLower.Value = this.NudUpper.Value; }



This is just a quick fix that will ensure that a valid range is entered. It is beyond the scope of this tutorial however.

Your form class should now look like this:

public partial class Form1 : Form
{

    public Form1()
    {
        InitializeComponent();
        this.NudUpper.Maximum = this.NudLower.Maximum = int.MaxValue;
    }

    private void BtnCalculate_Click(object sender, EventArgs e)
    {

          this.TxtNoOfPrimes.Text += this.getPrimesInRange(int.Parse(this.NudLower.Value.ToString()), int.Parse            (this.NudUpper.Value.ToString())).Count().ToString() + Environment.NewLine;
      
    }

    private IEnumerable<int> getPrimesInRange(int inclusiveStartValue, int exclusiveEndValue)
    {
        System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(1000));
        return getAllPrimes(Enumerable.Range(inclusiveStartValue, (exclusiveEndValue - inclusiveStartValue)));
    }

    private IEnumerable<int> getAllPrimes(IEnumerable<int> numbers)
    {
        return from n in numbers where (!(n <= 1) && Enumerable.Range(2, (int)Math.Sqrt(n == 2 ? 0 : n)).All(i => n % i > 0)) select n;
    }

    private void NudLower_Validating(object sender, CancelEventArgs e)
    {
        if (this.NudLower.Value > this.NudUpper.Value) { this.NudLower.Value = this.NudUpper.Value; }
    }
}



Go on and give it a test. Try entering a few large number ranges and then clicking calculate (note that you don’t have to use the arrows on the numeric up downs, you can enter your value manually by clicking in the control). What you should notice is that whilst one count is being done, the UI is left unresponsive and you cannot do anything further until that calculation is done.

However, we want the user to be able to calculate as many different ranges as they like, at the same time. They should be able to set one count going, and then immediately set another one going. The UI shouldn’t be frozen whilst other calculations are being completed.

It is at this point that many of you (I imagine) will jump straight towards creating a dedicated thread or using the background worker. However, I am going to use Tasks!

Firstly, add this using statement to you code:

using System.Threading.Tasks;


Delete the code currently in the calculate button’s click event handler, and add this code:

            //avoid cross thread accessing of controls by storing values from controls in variables     
            int lower = int.Parse(this.NudLower.Value.ToString());
            int upper = int.Parse(this.NudUpper.Value.ToString());
            
            //start a new task, and then display result in text box
            Task.Factory.StartNew<int>(() => 
                this.getPrimesInRange(lower, upper).Count());      





How simple was that! I have just wrapped the call to getPrimesInRange() in a task. That will now run asynchronously alongside the main UI thread, meaning that the UI will remain responsive at all times as it isn't being blocked by that horrible drawn out calculation!

Notice I am using the generic version of StartNew(). This just means that I want my task to return a value (in this case an int representing the count value). I would use the non generic equivalent if I didn’t want my task to return anything.

Right, so we have the task that will perform the calculation set up, however, we aren’t displaying the count in the text box yet. It is at this point that we would have to use Invoke() or BeginInvoke() to marshall a call onto the UI thread to allow us to update the text box if we were using a dedicated thread.

However, this too is very easy with tasks. Firstly, add this field to the top of your class:

private TaskScheduler scheduler = null;


Now, in the Form1 constructor (Form1()), add this line:

this.scheduler = TaskScheduler.FromCurrentSynchronizationContext();


This assigns the field a task scheduler for the UI thread, we will be able to use this to update our text box in a minute.

Next, we need to update the textbox with the value returned from our calculation task.

To do this, we use ContinueWith(). This is what allows us to chain tasks together, one after the other.

What we are going to is use ContinueWith() to perform a new task after the calculation task completes, of which will use the value returned from our first task to update the UI.

Change the code in the calculate button click event handler to this:


            int lower = int.Parse(this.NudLower.Value.ToString());
            int upper = int.Parse(this.NudUpper.Value.ToString());
            
            //start a new task, and then display result in text box
            Task.Factory.StartNew<int>(() => 
                this.getPrimesInRange(lower, upper).Count())
                .ContinueWith((i) => this.TxtNoOfPrimes.Text += i.Result.ToString() + Environment.NewLine, this.scheduler);      




This is the part that has been added:

 .ContinueWith((i) => this.TxtNoOfPrimes.Text += i.Result.ToString() + Environment.NewLine, this.scheduler);   



This is basically saying, when the first task is finished, perform this new task. The parameter ‘i’ is the first task (NOT the result, the completed task itself!). This must be passed into the delegate used to represent the continuation action (first parameter of ContinueWith()) in ContinueWith().

We get the result from the first (antecedent) task (our calculated count value), and put it in the text box as we did before. We access the result of the first task using it’s result property.

Notice how we pass in the value held in our TaskScheduler field into ContinueWith() as the second arguement. Now, because this comes from the context of UI thread, we can now update components on the UI freely, making cross thread updating very easy and logical! If we were to use a dedicated thread instead of tasks, we would have to use Invoke() or BeginInvoke() to marshall a call across onto the UI thread, if we wanted to update the UI.

Verify that this is the code you now have in your class:

public partial class Form1 : Form
{
	private TaskScheduler scheduler = null;

	public Form1()
	{
            InitializeComponent();
            this.NudUpper.Maximum = this.NudLower.Maximum = int.MaxValue;
	    this.scheduler = TaskScheduler.FromCurrentSynchronizationContext();
	}

	private void BtnCalculate_Click(object sender, EventArgs e) 
        {

            int lower = int.Parse(this.NudLower.Value.ToString());
            int upper = int.Parse(this.NudUpper.Value.ToString());
            
            //start a new task, and then display result in text box
            Task.Factory.StartNew<int>(() => 
              this.getPrimesInRange(lower, upper).Count())
              .ContinueWith((i) => this.TxtNoOfPrimes.Text += i.Result.ToString() + Environment.NewLine, this.scheduler);      
		
        }

	private IEnumerable<int> getPrimesInRange(int inclusiveStartValue, int exclusiveEndValue)
	{
	     System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(1000));
	     return getAllPrimes(Enumerable.Range(inclusiveStartValue, (exclusiveEndValue - inclusiveStartValue)));
	}

	private IEnumerable<int> getAllPrimes(IEnumerable<int> numbers)
	{
	     return from n in numbers where (!(n <= 1) && Enumerable.Range(2, (int)Math.Sqrt(n == 2 ? 0 : n)).All(i => n % i > 0)) select n;
	}

	private void NudLower_Validating(object sender, CancelEventArgs e)
	{
	      if (this.NudLower.Value > this.NudUpper.Value) { this.NudLower.Value = this.NudUpper.Value; }
	}
}



Now, run the application again. You should now find that the UI remains responsive at all times. You can now set a long running calculation going, and then IMMEDIATELY set another one going, and then another one, and another one and… The count values will appear one by one as each calculation completes! If you set a really long task going, then set a smaller one going, the smaller one will likely finish first and so will be displayed first!

Note how the work that tasks do is represented by a delegate that is passed in on the creation of the tasks. Much in the same way as a delegate is passed into new ThreadStart() and ParameterizedThreadStart() when creating a dedicated thread!


A few final points on tasks:

1) They are executed asynchronously (i.e. they don't block execution) by default, but they can be executed synchronously using the RunSynchronouly() method.

2) Tasks provide a parent/child behaviour when one task is started from another.

3)They provide conditional continuation options. For example, you may only want to execute the continuation task if the original task ran fully to completion.

4) They support cooperative cancellation options.


Here is a link for further information on tasks (pay particular attention to exception handling within tasks. It is quite convenient and neat how it is done!):

http://msdn.microsof...y/dd537609.aspx


Well, that’s it! I hope you found this tutorial enlightening, and I hope you shall at least consider Tasks when you next have a long running, err…task to perform, as I honestly believe that they are very important and useful to know. Not only is it a better way to do it than creating a dedicated thread, it's a easier, more logical way to do it, from the point of view of the human being that is programming the applications. You may not use them for every single long running operation you perform from now on, but they should be in the forefront of your mind when such occasions arise I think.

Moving on, it was a bit of a trivial example in this tutorial, but I think it demonstrated the idea on a very basic level. There is an awful lot more to tasks than I could have possibly mentioned here, however, this hopefully gave you a starting point!

This post has been edited by CodingSup3rnatur@l-360: 08 May 2013 - 11:19 AM


Is This A Good Question/Topic? 9
  • +

Replies To: Very Basic Introduction to Tasks

#2 eclipsed4utoo  Icon User is offline

  • Not Your Ordinary Programmer
  • member icon

Reputation: 1526
  • View blog
  • Posts: 5,961
  • Joined: 21-March 08

Posted 17 February 2011 - 06:25 AM

Very nice. Learned something new today.
Was This Post Helpful? 0
  • +
  • -

#3 Sergio Tapia  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 1253
  • View blog
  • Posts: 4,168
  • Joined: 27-January 10

Posted 12 May 2011 - 04:09 PM

This tutorial is fantastic. I'm going to try to implement Tasks in my little pet project I'm working on. :)

Thanks!
Was This Post Helpful? 0
  • +
  • -

#4 DivideByZero  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 238
  • View blog
  • Posts: 551
  • Joined: 02-December 10

Posted 11 June 2011 - 04:13 AM

You'd be glad to hear that while having a chat about threading with my employer, I told him my knowledge of threading to which he said "we prefer using tasks here".

So now I have even more incentive to learn from this tutorial now :)
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1