5.4. Tasks

Task is a new class that represents the work you want completed. There are methods to create, schedule, and synchronize tasks in your application.

5.4.1. Task Scheduler

All the complexity of working with tasks is handled by the task scheduler, which in turn works with the main .NET thread pool. You can think of a task as a wrapper for the thread pool and the preferred way of scheduling threads (although there is some additional overhead). The existing thread pool methods will continue to work, but tasks are much easier to use and have additional functionality.

So how does the task scheduler work?

  • When tasks are created, they are added to a global task queue.

  • The thread pool will create a number of "worker" threads. The exact number that are created depends on a variety of factors, such as the number of cores on the machine, the current workload, the type of workload, and so on. The thread pool utilizes a hill-climbing algorithm that dynamically adjusts the thread pool to use the optimum number of threads. For example, if the thread pool detects that many threads have an I/O bottleneck, it will create additional threads to complete the work more quickly. The thread pool contains a background thread that checks every 0.5 seconds to see whether any work has been completed. If no work has been done (and there is more work to do), a new thread will be created to perform this work.

  • Each worker thread picks up tasks from the global queue and moves it onto its local queue for execution.

  • Each worker thread processes the tasks on its queue.

  • If a thread finishes all the work in its local queue, it steals work from other queues to ensure that work is processed as quickly as possible. Note that tasks will steal work from the end of the other tasks' queues to minimize the chance that another task has started operating with the work already.

  • Figure 5-3 demonstrates this process.

    Figure 5.3. Overview of task manager

5.4.2. Creating a New Task

Tasks are very easy to schedule and I think more intuitive than working with traditional threading and the thread pool. There are a number of ways to create a new task, but before you see them, you need to add the following using directive because all the task functionality is found in the System.Threading.Tasks namespace:

using System.Threading.Tasks;

The easiest way to create a task is with the Task.Factory.StartNew() method. This method accepts an Action delegate and immediately starts the task when created.

Task task1 = Task.Factory.StartNew(() => Console.WriteLine("hello task 1"));

Another way to create a task is to pass the code you want run into the task's constructor. The main difference with this method is that you have to explicitly start the task when using this method. This method could be useful for scenarios in which you don't want the task to run as soon as it is declared:

Task task2 = new Task(() => Console.WriteLine("hello task 2"));
task2.Start();

5.4.3. Task.Wait() and Task.WaitAll()

The Task.Wait() and Task.WaitAll() methods allow you to pause the flow of execution until the tasks you specify have completed their work. The following listing shows an example of using the Wait() method to ensure that task1 has completed and the WaitAll() method to ensure that task2, task3, and task4 have finished before exiting the application:

Task task1 = Task.Factory.StartNew(() => Console.WriteLine("hello task 1"));
Task task2 = new Task(() => Console.WriteLine("hello task 2"));
Task task3 = Task.Factory.StartNew(() => Console.WriteLine("hello task 3"));
Task task4 = Task.Factory.StartNew(() => Console.WriteLine("hello task 4"));

task2.Start();

task1.Wait();
Task.WaitAll(task2, task3, task4);

Figure 5-4 illustrates the waiting process.

Figure 5.4. Flow of execution for the Task.Wait() example

5.4.4. Task.WaitAny()

You can wait for any task to complete with the Task.WaitAny() method. It could be used, for example, if many tasks were retrieving the same data (e.g., the latest Microsoft stock price) from a number of different sources and you didn't care which individual source you received the information from.

Task.WaitAny(task2, task3, task4);

5.4.5. IsCompleted

You can see whether a task is completed by querying the IsCompleted property. It returns a Boolean value indicating whether the task has completed its work.

while (task1.IsCompleted == false)
{
   Console.WriteLine("Waiting on task 1");
}

5.4.6. ContinueWith()

It is often necessary to specify that work should be performed in a specific order. This can be declared in a fluent manner with the ContinueWith() method. In previous examples, the tasks occurred out of the order in which they were created. If you want to enforce this order one way, you could use the ContinueWith() method as follows:

Task task3 = Task.Factory.StartNew(() => Console.WriteLine("hello task 1"))
  .ContinueWith((t)=>  Console.WriteLine("hello task 2") )
  .ContinueWith((t)=>  Console.WriteLine("hello task 3") )
  .ContinueWith((t)=>  Console.WriteLine("hello task 4") );

The ContinueWith() method also accepts a TaskContinuationOptions enumeration that allows you to specify what should occur if a task fails, as well as a number of other situations. The following code calls the stock service with Stocks[1] as a parameter if the previous task failed to run:

Task task3 = Task.Factory.StartNew(() => doSomethingBad())
  .ContinueWith((t) => System.Diagnostics.Trace.Write("I will be run"),
  TaskContinuationOptions.OnlyOnFaulted);

5.4.7. Do Parallel Loops Create a Thread for Each Iteration?

The answer is maybe but not necessarily. Tasks are created in order to perform the work as quickly as possible, but it is up to the task manager and scheduler to decide the optimum means to achieve this.

5.4.8. Returning Values from Tasks

You can retrieve a value that has been returned from a task by querying the result property:

var data = Task.Factory.StartNew(() => GetResult());
Console.WriteLine("Parallel task returned with value of {0}", data.Result);

An alternative method can be used if you are using the Task<T> type:

Task<string> t = new Task<string>(()=>GetResult());
t.Start();
Console.WriteLine("Parallel task returned with value of {0}", t.Result);

5.4.9. What if the Task Does Not Yet Have a Result?

If you try and access the result of a task, and the task has completed its work, the value will be returned as you would expect. If, however, the task has not completed, execution will block until the task has completed. This could slow your application down, as the CLR waits for a value to be returned. To minimize this, you probably want to run the task as soon as possible before you need access to the actual value.

5.4.9.1. Task Creation Options

When you create a task, you can specify hints to the scheduler about how the task should be scheduled using the TaskCreationOptions class:

  • AttachedToParent: Whether the task is attached to a parent task.

  • LongRunning: The task will run for a long time for optimal scheduling.

  • None: This is the default scheduling behavior.

  • PreferFairness: The tasks should be scheduled in the order in which they are created.

5.4.9.2. Task Status

Tasks can have the following status:

  • Cancelled: The task was cancelled before it reached running status, or the cancellation acknowledged and completed with no exceptions.

  • Created: The task was created but not initialized.

  • Faulted: The task completed due to an exception that was not handled.

  • RanToCompletion: The task completed successfully.

  • Running: The task is currently running.

  • WaitingForActivation: The task is waiting to be activated and scheduled.

  • WaitingForChildrenToComplete: The task is waiting for child tasks to complete.

  • WaitingToRun: The task is scheduled but has not yet run.

5.4.9.3. Overriding TaskScheduler

When tasks are created, they are scheduled using the default implementation of the TaskScheduler class (TaskScheduler.Default). TaskScheduler is abstract and can be overridden if you want to provide your own implementation.

5.4.9.4. Scheduling on the UI Thread

TaskScheduler supports the ability to schedule items on the UI thread, saving you from writing some tedious marshalling code. For more info on this, please refer to http://blogs.msdn.com/pfxteam/archive/2009/04/14/9549246.aspx.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.2.157