Using .NET Task Parallel Library (TPL) to run parallel tasks and update UI controls. Includes sample app.

When analyzing a programming task requirement, one of the questions a good developer asks themselves is if there’s any subtask which can be run in the background or asynchronously. For example, If a task needs to get data from two independent data sources, there’s really no need to run these two data retrieval tasks in sequence. The app would be faster and more scalable if these tasks run in parallel instead of in sequence.

In .NET it is customary to run these tasks in new threads. The BackgroundWorker class is also a way to do this asynchronously.

In .NET 4.0 there’s a new class library called the Task Parallel Library (TPL) which offers an abstraction over the creation and use of threads. TPL enables the use of multicore machines without worrying about coding details.

In this post, I will not get into detail about TPL and how it works. I will be showing an example of how to fire off two parallel tasks to simulate grabbing of data, showing how to update UI controls from a non UI thread and how to return data from these tasks.

I have a WinForm form which contains 3 buttons and a few textboxes and labels to display a task status and the data retrieved.

There are two identical tasks, each simulate retrieving a customer’s data from a source and returning the customer data to the UI. Each task is constructed in a way where it retrieves the data, displays its status, returns the data to the UI and the UI displays the data. Each task can be launched at any time independent of the other task, both can be launched simultaneously and the UI is still responsive.

Note: a thread can not update a UI control which was not created in the same thread.

 

Steps to create a task so it can launched in parallel or asynchronously & be able to update a UI control:

  1. Get a reference to the UI’s TaskScheduler. This is used for the initial task’s continuation. The continuation task is where the data is displayed.

  2. Get the current UI SynchronizationContext. We need this context to post a UI control update in the initial task (changing text in a textbox). Remember this task is running in a different thread than the UI thread. A non UI thread can not update a UI control. Because this is a non UI thread, anything happening here will not block the UI thread, freeing the UI to be responsive to user actions.

  3. Create and start a new Task. Inside this task the following happens:
    Update a textbox to display task’s status. Here we simulate a slow running process. This can be for example a web service which returns a customer data.

  4. A continuation task for the initial task runs when the initial task completes. This task is able to update UI controls because it’s using the UI’s TaskScheduler.

 

The steps above can be repeated for as many tasks as needed to run in parallel. There are TPL’s methods to wait for any or all of these tasks to complete before another continuation task uses their data. There are also methods for cancellations, exception handling, waiting plus more. I won’t be discussing these here. The TPL’s reference on Microsoft’s site has detailed information.

 

Proof that non UI threads can not update UI controls:

If you uncomment the statement which updates the textbox’s text from inside the task inside Visual Studio’s debugger, an InvalidOperation exception occurs with message “Cross-thread operation not valid: Control 'txtTask1' accessed from a thread other than the thread it was created on.”. However I am not sure why it works in non debugger mode. Always use post or use BeginInvoke to update UI controls from non UI threads.

 

The code below shows the form’s code. You can also download the Visual Studio project. ParallelDemo using TPL.zip (56.32 kb)

   1: public partial class Form1 : Form
   2:     {
   3:         public Form1()
   4:         {
   5:             InitializeComponent();
   6:             
   7:         }
   8:  
   9:         private void btnTask1_Click(object sender, EventArgs e)
  10:         {
  11:             GetCustomer1();
  12:         }
  13:  
  14:         private void GetCustomer1()
  15:         {
  16:             int customerID = 1;
  17:             lblFirstName1.Text = "";
  18:             lblLastName1.Text = "";
  19:             var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  20:  
  21:             SynchronizationContext synContext = SynchronizationContext.Current;
  22:  
  23:             Task<Customer> customerTask = Task.Factory.StartNew(() =>
  24:                                                                     {
  25:                                                                        // txtTask1.Text = "Running for 5 seconds...";
  26:                                                                         synContext.Post(
  27:                                                                             state => txtTask1.Text = (string) state,
  28:                                                                             "Running for 4 seconds...");
  29:                                                                         Customer newCustomer = new Customer();
  30:                                                                         newCustomer.CustomerID = customerID;
  31:                                                                         newCustomer.FirstName = "John";
  32:                                                                         newCustomer.LastName = "Smith";
  33:                                                                         Thread.Sleep(4000);
  34:                                                                         return newCustomer;
  35:                                                                     });
  36:  
  37:  
  38:             customerTask.ContinueWith(t =>
  39:                                           {
  40:                                               lblFirstName1.Text = "First name: " + t.Result.FirstName;
  41:                                               lblLastName1.Text = "Last name: " + t.Result.LastName;
  42:                                               txtTask1.Text = "Done.";
  43:                                           }, uiScheduler);
  44:         }
  45:  
  46:  
  47:         private void button2_Click(object sender, EventArgs e)
  48:         {
  49:             GetCustomer2();
  50:         }
  51:  
  52:  
  53:         private void GetCustomer2()
  54:         {
  55:             int customerID = 2;
  56:             lblFirstName2.Text = "";
  57:             lblLastName2.Text = "";
  58:             var uiScheduler =
  59:                 TaskScheduler.FromCurrentSynchronizationContext();
  60:  
  61:             CancellationTokenSource tokenSource
  62:                 = new CancellationTokenSource();
  63:  
  64:             // create the cancellation token 
  65:             CancellationToken cancellationToken = tokenSource.Token;
  66:             SynchronizationContext synContext = SynchronizationContext.Current;
  67:  
  68:  
  69:             Task<Customer> customerTask = Task.Factory.StartNew(() =>
  70:                                                                     {
  71:                                                                         /* txtTask2.Text =  "Running for 5 seconds...";*/
  72:                                                                         synContext.Post(
  73:                                                                             state => { txtTask2.Text = (string) state; },"Running for 5 seconds...");
  74:                                                                         Customer newCustomer = new Customer();
  75:                                                                         newCustomer.CustomerID = customerID;
  76:                                                                         newCustomer.FirstName = "Jane";
  77:                                                                         newCustomer.LastName = "Doe";
  78:                                                                         Thread.Sleep(5000);
  79:                                                                         return newCustomer;
  80:                                                                     });
  81:  
  82:  
  83:             customerTask.ContinueWith(t =>
  84:                                           {
  85:                                               lblFirstName2.Text = "First name: " + t.Result.FirstName;
  86:                                               lblLastName2.Text = "Last name: " + t.Result.LastName;
  87:                                               txtTask2.Text = "Done.";
  88:                                           }, uiScheduler);
  89:         }
  90:  
  91:  
  92:         private void btnStartAll_Click(object sender, EventArgs e)
  93:         {
  94:             GetCustomer1();
  95:             GetCustomer2();
  96:         }
  97:     }
  98:  
  99:  
 100:     public class Customer
 101:     {
 102:         public int CustomerID { get; set; }
 103:         public string FirstName { get; set; }
 104:         public string LastName { get; set; }
 105:     }
separator

Using .NET Task Parallel Library (TPL) to run parallel tasks and update UI controls. Includes sample app.

When analyzing a programming task requirement, one of the questions a good developer asks themselves is if there’s any subtask which can be run in the background or asynchronously. For example, If a task needs to get data from two independent data sources, there’s really no need to run these two data retrieval tasks in sequence. The app would be faster and more scalable if these tasks run in parallel instead of in sequence.

In .NET it is customary to run these tasks in new threads. The BackgroundWorker class is also a way to do this asynchronously.

In .NET 4.0 there’s a new class library called the Task Parallel Library (TPL) which offers an abstraction over the creation and use of threads. TPL enables the use of multicore machines without worrying about coding details.

In this post, I will not get into detail about TPL and how it works. I will be showing an example of how to fire off two parallel tasks to simulate grabbing of data, showing how to update UI controls from a non UI thread and how to return data from these tasks.

I have a WinForm form which contains 3 buttons and a few textboxes and labels to display a task status and the data retrieved.

There are two identical tasks, each simulate retrieving a customer’s data from a source and returning the customer data to the UI. Each task is constructed in a way where it retrieves the data, displays its status, returns the data to the UI and the UI displays the data. Each task can be launched at any time independent of the other task, both can be launched simultaneously and the UI is still responsive.

Note: a thread can not update a UI control which was not created in the same thread.

 

Steps to create a task so it can launched in parallel or asynchronously & be able to update a UI control:

  1. Get a reference to the UI’s TaskScheduler. This is used for the initial task’s continuation. The continuation task is where the data is displayed.

  2. Get the current UI SynchronizationContext. We need this context to post a UI control update in the initial task (changing text in a textbox). Remember this task is running in a different thread than the UI thread. A non UI thread can not update a UI control. Because this is a non UI thread, anything happening here will not block the UI thread, freeing the UI to be responsive to user actions.

  3. Create and start a new Task. Inside this task the following happens:
    Update a textbox to display task’s status. Here we simulate a slow running process. This can be for example a web service which returns a customer data.

  4. A continuation task for the initial task runs when the initial task completes. This task is able to update UI controls because it’s using the UI’s TaskScheduler.

 

The steps above can be repeated for as many tasks as needed to run in parallel. There are TPL’s methods to wait for any or all of these tasks to complete before another continuation task uses their data. There are also methods for cancellations, exception handling, waiting plus more. I won’t be discussing these here. The TPL’s reference on Microsoft’s site has detailed information.

 

Proof that non UI threads can not update UI controls:

If you uncomment the statement which updates the textbox’s text from inside the task inside Visual Studio’s debugger, an InvalidOperation exception occurs with message “Cross-thread operation not valid: Control 'txtTask1' accessed from a thread other than the thread it was created on.”. However I am not sure why it works in non debugger mode. Always use post or use BeginInvoke to update UI controls from non UI threads.

 

The code below shows the form’s code. You can also download the Visual Studio project. ParallelDemo using TPL.zip (56.32 kb)

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

        private void btnTask1_Click(object sender, EventArgs e)
        {
            GetCustomer1();
        }

        private void GetCustomer1()
        {
            int customerID = 1;
            lblFirstName1.Text = "";
            lblLastName1.Text = "";
            var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

            SynchronizationContext synContext = SynchronizationContext.Current;

            Task<Customer> customerTask = Task.Factory.StartNew(() =>
                                                                    {
                                                                       // txtTask1.Text = "Running for 5 seconds...";
                                                                        synContext.Post(
                                                                            state => txtTask1.Text = (string) state,
                                                                            "Running for 4 seconds...");
                                                                        Customer newCustomer = new Customer();
                                                                        newCustomer.CustomerID = customerID;
                                                                        newCustomer.FirstName = "John";
                                                                        newCustomer.LastName = "Smith";
                                                                        Thread.Sleep(4000);
                                                                        return newCustomer;
                                                                    });


            customerTask.ContinueWith(t =>
                                          {
                                              lblFirstName1.Text = "First name: " + t.Result.FirstName;
                                              lblLastName1.Text = "Last name: " + t.Result.LastName;
                                              txtTask1.Text = "Done.";
                                          }, uiScheduler);
        }


        private void button2_Click(object sender, EventArgs e)
        {
            GetCustomer2();
        }


        private void GetCustomer2()
        {
            int customerID = 2;
            lblFirstName2.Text = "";
            lblLastName2.Text = "";
            var uiScheduler =
                TaskScheduler.FromCurrentSynchronizationContext();

            CancellationTokenSource tokenSource
                = new CancellationTokenSource();

            // create the cancellation token 
            CancellationToken cancellationToken = tokenSource.Token;
            SynchronizationContext synContext = SynchronizationContext.Current;


            Task<Customer> customerTask = Task.Factory.StartNew(() =>
                                                                    {
                                                                        /* txtTask2.Text =  "Running for 5 seconds...";*/
                                                                        synContext.Post(
                                                                            state => { txtTask2.Text = (string) state; },"Running for 5 seconds...");
                                                                        Customer newCustomer = new Customer();
                                                                        newCustomer.CustomerID = customerID;
                                                                        newCustomer.FirstName = "Jane";
                                                                        newCustomer.LastName = "Doe";
                                                                        Thread.Sleep(5000);
                                                                        return newCustomer;
                                                                    });


            customerTask.ContinueWith(t =>
                                          {
                                              lblFirstName2.Text = "First name: " + t.Result.FirstName;
                                              lblLastName2.Text = "Last name: " + t.Result.LastName;
                                              txtTask2.Text = "Done.";
                                          }, uiScheduler);
        }


        private void btnStartAll_Click(object sender, EventArgs e)
        {
            GetCustomer1();
            GetCustomer2();
        }
    }


    public class Customer
    {
        public int CustomerID { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
separator

Place the search box at the top of the web page

 

searchbox

 

It's customary to place heavily used links such as the navigation bar somewhere where they are highly accessible. Usually that would be towards the top of the page. The search box is one of most useful functions in a web site, specially when the site has a lot of content and does not have very accurate or usable navigation links.

The informal standard is to place it at the top right section of the home page and preferably in every web page.

I have seen some search boxes sandwiched in somewhere among another links in the right bar or in the footer of the web page, such as the one shown in the top image.

If the user can't see the search box above the fold in clear view, they will assume the site doesn't have a search function and not look for it. Imagine if Amazon had its search bar below the fold or in the footer.

Being different at the expense of usability is not a good idea.

separator

I Upgraded my blog's theme and software

 

I upgraded the blog's software to the latest version of BlogEngine, v2.5. I took the chance and did a makeover using another template. I tweaked a WordPress theme to work under BlogEngine. This required updates to the HTML, CSS, JavaScript and the server .NET code. I liked the old theme, shown below but I decided to use a theme which is more quaint and mellow. This time one of the requirements was to use a white background for the posts. It's easier to display images with that background.

BlogEngine's theming framework could have been more flexible if it didn't create dynamic HTML which had no css styles. I would suggest to the developers who maintain BlogEngine to add some empty classes in the code which creates HTML. This way non developers can add the classes declarations in the css files.

 

The new features added to the blog are:

  • Use Disqus as the comment platform. The older blog used the internal comment system. I noticed a lot of comment spam displaying in the blog even though I was using Akisment & a captcha. The spam was from the same ip address so this was a determined spammer. I am not More...
separator