Click here to Skip to main content
15,867,895 members
Articles / Programming Languages / C#

Multithreading using Task Factory, C#, Basic Sample

Rate me:
Please Sign up or sign in to vote.
4.65/5 (34 votes)
24 Nov 2014CPOL7 min read 106.4K   2.7K   71   19
This is a small basic sample that shows you how to quickly set your multi-threaded environment using the new C# Task Factory.

Introduction

In my previous article (MultiThreading Using a Background Worker, C#), I talked about Background Workers, which were the easiest and most effective way of creating multi-threaded desktop applications. However, this technique is becoming obsolete, now the new Task Factory is here. There are many nice articles on this subject, detailed and well organized, like: Task Parallel Library and async-await Functionality - Patterns of Usage in Easy Samples, and the 6 part tutorial: Task Parallel Library: 1 of n. However, I wanted this article to be a sequel of my previous one, and to show you how to use the basic features of the Task Factory. I think that users who are migrating from Background workers will find this easy to follow since I wrote it as close as it can get to the technique we used in background workers, I intended not to use lambda expressions, although I encourage you to use them.

Background

Developers who have written desktop applications know how it feels when performing heavy duty operations, or when requesting data from a remote location. The user interface will freeze till the operation is completed. This is because the application is running on a single thread, and when this thread is busy doing some other stuff, the application will freeze till this thread is ready once again to render the user interface to the user. Here, multithreading comes to the rescue: you delegate your heavy work to another thread, and thus your application will remain responsive during the time of the operation. you can also make use of the multi-processing capability of the user's machine to distribute the job among different threads/tasks. For our sample, we will be using the Task Factory class.

Implementing the Sample

I will be using Visual studio 2012 for this project. Unfortunately, some of the features used here are not available in Visual Studio 2010 or earlier (like the async keyword), or in the best cases, hard to add and will end up in hidden bugs.

Begin by creating a new Windows Forms applications and set your design as below. I personally like to use table layout panels to design my forms, you can check a small tip I have written on this to give you a better idea about this control: Designing the Layout of Windows Forms using a TableLayoutPanel, with auto-expand panels.

Image 1

Basically, we will have a text box (set to multiline mode) to show the results coming from our working thread, a numeric box to allow us to choose a number, a start button and a cancel button. We will also have a status strip with a status label, to show the progress from our task.

From the toolbox menu, under the Menus & Toolbars section, add a "Status Strip".

Image 2

 

Inside the status strip, click the small arrow on the left corner and add a "Status Label". Rename the label to lblStaus, and set its Text property to an empty string.

Image 3

Before we begin, keep in mind that only the main thread has access to the user controls, so we have to capture the user input from the main thread, and pass it to the background thread somehow.

Right click your form and select "View Code", type down the below method:

C#
private async void RunTask()
       {
           int numericValue = (int)numericUpDown1.Value;//Capture the user input
           object[] arrObjects = new object[] { numericValue };//Declare the array of objects

           using (Task<string> task = new Task<string>(new Func<object,
           string>(PerfromTaskAction), arrObjects, cancellationToken))//Declare and
                                                                      //initialize the task
           {
               lblStatus.Text = "Started Calculation...";//Set the status label to signal
                                                         //starting the operation
               btnStart.Enabled = false; //Disable the Start button
               task.Start();//Start the execution of the task
               await task;// wait for the task to finish, without blocking the main thread

               if (!task.IsFaulted)
               {
                   textBox1.Text = task.Result.ToString();//at this point,
                   //the task has finished its background work, and we can take the result
                   lblStatus.Text = "Completed.";//Signal the completion of the task
               }
               btnStart.Enabled = true; //Re-enable the Start button
           }
       }

Here, we first got the user input from the numeric box, created an array of objects, then added the value from the numeric box to this array. We will be passing this array of objects to the background thread, since only the main thread has access to the user controls. After that, we will initialize a Task<String> object, the <String>means that our Task will return a String object.

After that, we set our status label to "Started Calculation...", to signal to the user that our background operation has started. Then we will start our Task, using task.Start();. Then we will wait for the task using the await command. This is different from the wait command because it does not block the main thread, the execution will be done asynchronously, thus the use of the async keyword in the method declaration.

Now, before we write the code that will be executed in the background thread, let us write a simple method that will simulate a heavy operation (call to a remote server, request data from database, complex operation...), we will just call Thread.Sleep for a 100 milliseconds before returning the result:

C#
private int PerformHeavyOperation(int i)
        {
            System.Threading.Thread.Sleep(100);
            return i * 1000;
        }

Now, we create the method that will be executed by the Task in the background thread, similar to DoWork event in the background worker:

C#
private string PerfromTaskAction(object state)
        {
            object[] arrObjects = (object[])state;//Get the array of objects from the main thread
            int maxValue = (int)arrObjects[0];//Get the maxValue integer from the array of objects

            StringBuilder sb = new StringBuilder();//Declare a new string builder to build the result

            for (int i = 0; i < maxValue; i++)
            {
                sb.Append(string.Format("Counting Number: {0}{1}", 
                PerformHeavyOperation(i), 
                Environment.NewLine));//Append the result to the string builder
            }

            return sb.ToString();//return the result
        }

Finally, double click the start button, and type the below in the click event handler for this button. This will start our task.

C#
private void btnStart_Click(object sender, EventArgs e)
      {
          RunTask();
      }

Run the form, and click start, you will notice that the form will begin to calculate, remain responsive during the calculation period, and finally will show you the desired result.

Image 4

Reporting Progress from the Task

It would be nice if we can show the user the progress of our operation, like a status message or a loading progress bar. As we have mentioned before, we cannot access the user interface directly from the background thread, thus we must find a way to report progress to the main thread from the background thread. For this, we will use a Progress<T> object. In my sample, I will be reporting progress as a string, thus declare an object at the top of your code of Type Progress<String> like below:

C#
Progress<string> progressManager = 
new Progress<string>();//Declare the object that will manage progress, and 
		//will be used to get the progress form our background thread

In the form constructor, add the following line of code, this will set the progress changed event.

C#
progressManager.ProgressChanged += progressManager_ProgressChanged;//Set the Progress changed event

The form constructor will now look like this:

C#
public Form1()
       {
           InitializeComponent();
           progressManager.ProgressChanged += progressManager_ProgressChanged;//Set the
                                                                              //Progress changed event
       }

Implement the ProgressChanged event, we are just setting the text we received from the background thread to our status label. This event is fired inside the main thread, that's why we are able to access the status label.

C#
void progressManager_ProgressChanged(object sender, string e)
       {
           lblStatus.Text = e;
       }

Change your perform Task Action method to the below, notice how we are using the progress manager to report progress from our background thread:

C#
private string PerfromTaskAction(object state)
   {
       object[] arrObjects = (object[])state;//Get the array of objects from the main thread
       int maxValue = (int)arrObjects[0];//Get the maxValue integer from the array of objects

       StringBuilder sb = new StringBuilder();//Declare a new string builder to build the result

       for (int i = 0; i < maxValue; i++)
       {
           sb.Append(string.Format("Counting Number: {0}{1}",
           PerformHeavyOperation(i), Environment.NewLine));//Append the result
                                                           //to the string builder
           ((IProgress<string>)progressManager).Report(string.Format
           ("Now Counting number: {0}...", i));//Report our progress to the main thread
       }

       return sb.ToString();//return the result
   }

Now run your form, you will notice that the label will show you the progress update:

Image 5

Canceling a Running Task

It is always desirable to allow the user to cancel a task that is taking too long to complete, or in case the user is not interested anymore in the result.

To cancel a running task, we will need a Cancellation Token, and to get a cancellation token we will need a cancellation token source. Luckily, there are two Microsoft classes that give you exactly what you want, the CancellationTokenSource and the CancellationToken. Begin by declaring a CancellationTokenSource object, and then declare a CancellationToken object:

C#
CancellationTokenSource cancellationTokenSource; //Declare a cancellation token source
CancellationToken cancellationToken; //Declare a cancellation token object,
   //we will populate this token from the token source, and pass it to the Task constructor.

Double click your "Cancel" button and add the following line of code to the event handler, this will issue a cancellation request to the cancellation token:

C#
cancellationTokenSource.Cancel();

In your RunTask method, add the cancellation token to the constructor of your Task, also initialize the CancellationTokenSource object, and give the CancellationToken a new Token. We have to do this before each start of the task, because cancellation tokens can't be reused after they have been canceled, if we attempt to run a task that has its cancellation token in the canceled state, you will get a runtime error.

C#
private async void RunTask()
       {
           int numericValue = (int)numericUpDown1.Value;//Capture the user input
           object[] arrObjects = new object[] { numericValue };//Declare the array of objects

           //Because Cancellation tokens cannot be reused after they have been canceled,
           //we need to create a new cancellation token before each start
           cancellationTokenSource = new CancellationTokenSource();
           cancellationToken = cancellationTokenSource.Token;

           using (Task<string> task = new Task<string>(new Func<object,
           string>(PerfromTaskAction), arrObjects, cancellationToken))//Declare and initialize the task
           {
               lblStatus.Text = "Started Calculation...";//Set the status label to signal
                                                         //starting the operation
               btnStart.Enabled = false; //Disable the Start button
               task.Start();//Start the execution of the task
               await task;// wait for the task to finish, without blocking the main thread

               if (!task.IsFaulted)
               {
                   textBox1.Text = task.Result.ToString();//at this point,
                       //the task has finished its background work, and we can take the result
                   lblStatus.Text = "Completed.";//Signal the completion of the task
               }

               btnStart.Enabled = true; //Re-enable the Start button
           }
       }

Change your PerformTaskAction method to check for cancellation requests at each iteration in your loop, if you find out that the user issued a cancellation request, you break out of the loop, thus bring the execution of the background thread to an end. You can check if a cancellation request is pending by checking the IsCancellationRequested property of the Cancellation Token. Another method will be to use the CancellationToken.ThrowIfCancellationRequested() method to throw an AggregateException that will stop your background thread, and you can catch this exception from the main thread to know that the task was canceled.

C#
private string PerfromTaskAction(object state)
        {
            object[] arrObjects = (object[])state;//Get the array of objects from the main thread
            int maxValue = (int)arrObjects[0];    //Get the maxValue integer from the array of objects

            StringBuilder sb = new StringBuilder();//Declare a new string builder to build the result

            for (int i = 0; i <= maxValue; i++)
            {
                if (cancellationToken.IsCancellationRequested)//Check if a cancellation request 
                                                              //is pending
                {
                    break;
                }
                else
                {
                    sb.Append(string.Format("Counting Number: {0}{1}", 
                    PerformHeavyOperation(i), Environment.NewLine));//Append the result 
                                                                    //to the string builder
                    ((IProgress<string>)progressManager).Report(string.Format
                    ("Now Counting number: {0}...", i));//Report our progress to the main thread
                }
            }

            return sb.ToString();//return the result
        }

Now try it, run the form and try to cancel your task while it is running, and check if your code works. You can download the complete sample source code from the top of the link, thanks for following up :).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 1 Pin
qdev7628-Jan-16 4:38
qdev7628-Jan-16 4:38 
QuestionAlternative and more efficient Pin
Graeme_Grant9-Jan-16 12:13
mvaGraeme_Grant9-Jan-16 12:13 
Answermy vote of 2 - poor code, and untyped parameter-transfer Pin
Mr.PoorEnglish21-Dec-14 14:42
Mr.PoorEnglish21-Dec-14 14:42 
GeneralRe: my vote of 2 - poor code, and untyped parameter-transfer Pin
Hassan Mokdad22-Dec-14 9:29
Hassan Mokdad22-Dec-14 9:29 
GeneralRe: my vote of 2 - poor code, and untyped parameter-transfer Pin
Mr.PoorEnglish9-Sep-15 9:52
Mr.PoorEnglish9-Sep-15 9:52 
GeneralRe: my vote of 2 - poor code, and untyped parameter-transfer Pin
Hassan Mokdad10-Sep-15 2:31
Hassan Mokdad10-Sep-15 2:31 
Hi, It has been a long time but please read my comments below on your reply:

Nevertheless I'd like to give some replies:
1) "You name surely describes you, seems you can't read neither English nor code"
such statements are bad behavior
Hassan: You are right, sorry for this

2) You said that my title mentioned Task Factory, but my code doesn't, dude did you even read the code, does the object Task mean anything to you?!!!
Yes, sometimes I use Task-Objects. But let me tell you a secret: A Task is not a TaskFactory! OMG | OMG | :OMG:
TaskFactory is a class in the Framework, and your code definitely makes no usage of it (in contradiction to your articles title).
Hassan: You are right, didn't know that.

3) You said that the usage of Cancelationtoken seems to be suboptimal, I understand this, can you please show us the optimanl way?
How about you - can you read code? Please give my snippet tryal: simply comment out your Form1-CodeBehind, and paste in the complete snippet I gave in my post before.
Start it and verify, that the application behaves exact the same.

4) You also didn't like the code style, this is something i understand and respect, but how can a code so basic has a simpler and agile style than the one used?
Look a snippet of your code:
using (Task<string> task = new Task<string>(new Func<object, string="">(PerfromTaskAction), arrObjects, cancellationToken))//Declare and initialize the task
{
lblStatus.Text = "Started Calculation...";//Set the status label to signal starting the operation
btnStart.Enabled = false; //Disable the Start button
task.Start();//Start the execution of the task
await task;// wait for the task to finish, without blocking the main thread

if (!task.IsFaulted)
{
textBox1.Text = task.Result.ToString();//at this point, the task has finished its background work, and we can take the result
lblStatus.Text = "Completed.";//Signal the completion of the task
}

btnStart.Enabled = true; //Re-enable the Start button
}
line #1 - strange comment. Correct would be: 'Create a task-Object' (But every c#-programmer knows by himself, that the new-keyword creats an object of the specified type)

#2 - unnecessary comment, since everybody (even me) can see by himself, that the labels Text is set, and what the Text sais.

#3 - unnecessary comment, since everybody can see by himself, that the button gets disabled

#4 - a still more pointless comment

#5 - the comment explains it wrong. The await-keyword makes nothing wait. What really happens is much more complicated: in fact the method exits at this point, and the rest of the code will be executed later, within a "continuation".
Hassan: Thanks for explaining

#6 - Why do you append .ToString() to the Task-Result? Another secret: The Result-Property of a Task<string> is already a string

the whole thing would have been done much better within only five lines:
btnStart.Enabled = false;
lblStatus.Text = "Started Calculation...";
textBox1.Text = await Task.Run(() => PerfromTaskAction(maxValue, cancelSource.Token));
lblStatus.Text = "Completed.";
btnStart.Enabled = true;
Especially watch the third line: the await-Keyword retrieves the task-result, and the anonymous Method passes the maxValue-object directly and strongly typed to the parallel execution - without doing wired things like:
Hassan Mokdad wrote:
object[] arrObjects = new object[] { numericValue };//Declare the array of objects
//...later...
object[] arrObjects = (object[])state;//Get the array of objects from the main thread
int maxValue = (int)arrObjects[0];//Get the maxValue integer from the array of obejcts

Hassan: You are also right about the code commenting, most of them are obvious, but that is the way I wrote my tutorial, and I appreciate your comments and notes

Hassan Mokdad wrote:
1) You don't show, how the await-Keyword directly retrieves the Task-Result. Hassan: Did you read the code? how can the await retrieve the result directly, please show me.
2) You don't show how to transfer strong typed objects to Task. Imo in an article about Threading with Tasks, omitting that feature is nogo. Hassan: If you read the artilce you will notice that I did mention how to pass strongly typed objects, both for the progress class and for the background thread. Please read carefully the next time
this i don't understand at all.
As said: Just give my snippet a tryal, and learn, 1) how the await-Keyword directly retrieves the Task-Result and 2) how one transfers strong typed objects to task.
And I made no statement about youre style of transferring objects to the progress class - or did i?
QuestionNice job! Pin
Ravi Bhavnani25-Nov-14 11:26
professionalRavi Bhavnani25-Nov-14 11:26 
AnswerRe: Nice job! Pin
Hassan Mokdad25-Nov-14 20:19
Hassan Mokdad25-Nov-14 20:19 
QuestionMy vote of 4 Pin
jrooks101725-Nov-14 1:59
professionaljrooks101725-Nov-14 1:59 
AnswerRe: My vote of 4 Pin
Hassan Mokdad25-Nov-14 2:20
Hassan Mokdad25-Nov-14 2:20 
GeneralRe: My vote of 4 Pin
jrooks101725-Nov-14 2:37
professionaljrooks101725-Nov-14 2:37 
GeneralRe: My vote of 4 Pin
Hassan Mokdad25-Nov-14 2:52
Hassan Mokdad25-Nov-14 2:52 
AnswerRe: My vote of 4 Pin
Member 111570422-Dec-14 19:10
Member 111570422-Dec-14 19:10 
GeneralRe: My vote of 4 Pin
jrooks10173-Dec-14 0:55
professionaljrooks10173-Dec-14 0:55 
GeneralRe: My vote of 4 Pin
Graeme_Grant9-Jan-16 11:59
mvaGraeme_Grant9-Jan-16 11:59 
GeneralMy vote of 5 Pin
Thomas Maierhofer (Tom)24-Nov-14 19:42
Thomas Maierhofer (Tom)24-Nov-14 19:42 
GeneralRe: My vote of 5 Pin
Hassan Mokdad24-Nov-14 20:48
Hassan Mokdad24-Nov-14 20:48 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun24-Nov-14 18:55
Humayun Kabir Mamun24-Nov-14 18:55 
GeneralRe: My vote of 5 Pin
Hassan Mokdad24-Nov-14 20:48
Hassan Mokdad24-Nov-14 20:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.