Implementing Tasks Part 2


Taking cue from my previous post which can be found here, we already know the source and the reason of the error

“Cross-thread operation not valid: Control textBox1 accessed from a thread other than the thread it was created on”

So the next question is how to solve it?

Now I do not want to mechanically give you a code to solve this error. Let me introduce another Class called TaskScheduler .The execution of Tasks is handled by a Task Scheduler.!

The Task class does not provide its own execution, instead it is handled by the Task Scheduler Class. The Task class is basically a wrapper round the delegate that represents the code inside the Task. We can also use Task Synchronously via the Task.RunSynchronously() Method. The Task class adds functionality on the top of the Delegate.

Now since we know, that the TaskScheduler does the work of putting the task to threads. Let’s take a closer look at how threads are managed in .NET 4.

   1: void QueueTasks()

   2:

   3: {

   4:

   5: // TaskA is a top level task.

   6:

   7: Task taskA = Task.Factory.StartNew( () =>

   8:

   9: {

  10:

  11: Console.WriteLine("I was enqueued on the thread pool's global queue.");

  12:

  13: // TaskB is a nested task and TaskC is a child task. Both go to local queue.

  14:

  15: Task taskB = new Task( ()=> Console.WriteLine("I was enqueued on the local queue."));

  16:

  17: Task taskC = new Task(() => Console.WriteLine("I was enqueued on the local queue, too."),

  18:

  19: TaskCreationOptions.AttachedToParent);

  20:

  21: taskB.Start();

  22:

  23: taskC.Start();

  24:

  25: });

  26:

  27: }

.

clip_image002To understand the code we must delve into the Task Scheduling concept of .NET 4.0.

Like earlier versions of .NET, 4.0 also has the concept of Global FIFO Thread Pool. Previously on encountering QueueUserWorkItem , the work is put into a queue and as soon as the thread becomes available, the work is put into it. This queue has been improved upon by .NET 4 to provide a lock free environment. So in era before .NET 4, each task would have been allotted a place in the global queue. So Task A, Task B, Task C will all be waiting at the global queue for a thread to become free. But .NET 4.0 introduces the concept of Local queue. So here Task A waits at the global queue but Task B and Task C wait at the local queue. A child or nested task is put on a local queue that is specific to the thread on which the parent task is executing. So when a thread is ready for work it first looks into the local queue and then the global queue. This approach has a number of benefits, for example tasks in local queues frequently access similar data and hence can benefit from the cache.

Now the first question is what happens if I do not want my task to be at the local or global queue.If its long running and might block other work items. We can specify a Long running option so that we can avoid the ThreadPool completely.

So now we understand what a “context” is .Here Context for task B is task A. Since basically task B is in the local queue for running on the thread of task A.

So, .NET 4 gives us a way to specify that a task should be scheduled to run on a particular thread.

Now coming back to our topic of solving the error “Cross-thread operation not valid: Control textBox1 accessed from a thread other than the thread it was created on”. We also know that we can reference UI element only from the UI thread. So the most obvious solution would be to run the task in the UI thread.So intstead of T.start() we put

T.Start(TaskScheduler.FromCurrentSynchronizationContext());

But then,this is not the correct way!!! resource Since if we run the entire task in one context, then basically the task and the UI thread are running on the same thread. Hence we are doing away with parallelism and asynchronousness. Our app will again freeze.

So what we basically want is that our intensive task will run on a separate thread but when it comes to updating the UI, the task should run in the context of the UI thread.

   1: int seconds = 0,j=0;

   2:

   3: Task T = Task.Factory.StartNew ( ()=> {

   4:

   5: int start_Time = System.Environment.TickCount;

   6:

   7: j=calculatePi(10000000);

   8:

   9: int stop_Time = System.Environment.TickCount;

  10:

  11: seconds = stop_Time - start_Time;

  12:

  13: });

  14:

  15: Task T_new= T.ContinueWith((antecedent) =>

  16:

  17: {

  18:

  19: this.textBox1.Text = j.ToString();

  20:

  21: this.label2.Text = seconds.ToString();

  22:

  23: }, TaskScheduler.FromCurrentSynchronizationContext());

  24:

  25: // T.Start(TaskScheduler.FromCurrentSynchronizationContext());

  26:

  27: T.Start();

  28:

  29: }

So first we make a task containing only the CPU intensive code. Then we make another task T_new and we say that the task needs to continue with the antecedent (the task that just completed). This T_new starts when T finishes and it runs on the current UI thread. Thus solving our problem.You might have noticed that I have used

Task.Factory.StartNew to create tasks.This is just a more efficient and faster way that improves the performance. The underlying principles don’t change.Instead of two lines , one for declaring the task and one for starting it, this does it in one line. Just a tad efficient.

So you can see how the TaskSwitcher class makes life easy for the developers.If we did not have the Task Switcher class we would have to code depending on the UI we were targeting.Like if we wanted to change from the worker thread to Ui thread,we would have to use control.Invoke or Control.BeginInvoke.But if we were to target WPF, we would have to change the code to Dispatcher.Invoke or Dispatcher.BeginInvoke .Thus two different codes for two different platforms.But here through the task switcher class,we can target UI irrespecting of whether it being WPF or WindowsForms.

So now after we edited the app. Lets run it and click on Parallel 4 Times. Here is the result

clip_image003
My CPU has 4 cores and since I clicked 4 times, each one executed single thread.

Advertisements

One response to “Implementing Tasks Part 2

  1. Pingback: Asynchronous Programming Series « Using Abhik.Mitra.myThoughts;

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s