So far, you’ve seen some of the most common ways to use futures and continuation tasks to create tasks. This section describes some other ways to use them.
1. Canceling Futures and Continuation Tasks
There are several ways to
cancel futures and continuation tasks. You can handle cancellation
entirely from within the task, as the Adatum Dashboard does, or you can
pass cancellation tokens when the tasks are created.
The Adatum Dashboard application supports cancellation from the user interface. It does this by calling the Cancel method of the CancellationTokenSource class. This sets the IsCancellation Requested property of the cancellation token to true.
The application checks for this
condition at various checkpoints. If a cancellation has been requested,
the operation is canceled.
2. Continue When “At Least One” Antecedent Completes
It’s possible to invoke a continuation task when the first of multiple antecedents completes. To do this, use the Task.Factory object’s ContinueWhenAny method. The ContinueWhenAny
method is useful when the result of any of the tasks will do. For
example, you may have an application where each task queries a Web
service that gives the local weather. The application returns the first
answer it receives to the user.
3. Using .Net Asynchronous Calls with Futures
Tasks are similar in some ways to asynchronous methods that use the .NET Asynchronous Programming Model (APM) pattern and the IAsyncResult interface. In fact, tasks in .NET Framework 4 are IAsync Result objects. They implement this interface. This allows you to use the Task class when you implement the APM pattern.
You can convert a pair of begin/end methods that use IAsync Result into a task. To do this, use the Task.Factory object’s From Async method.
In general, tasks can be easier to use than other implementations of IAsyncResult because futures rethrow exceptions when the result is requested.
Note:
Tasks in .NET implement the IAsyncResult interface.
4. Removing Bottlenecks
The idea of a critical path
is familiar from project management. A “path” is any sequence of tasks
from the beginning of the work to the end result. A task graph may
contain more than one path.
You can see that there are three paths, beginning with “Load NYSE,”
“Load Nasdaq,” and “Load Fed Historical Data” tasks. Each path ends with
the “Compare” task.
The duration of a path is the
sum of the execution time for each task in the path. The critical path
is the path with the longest duration. The amount of time needed to
calculate the end result depends only on the critical path. As long as
there are enough resources (that is, available cores), the noncritical
paths don’t affect the overall execution time.
If you want to make your task
graph run faster, you need to find a way to reduce the duration of the
critical path. To do this, you can organize the work more efficiently.
You can break down the slowest tasks into additional tasks, which can
then execute in parallel. You can also modify a particularly
time-consuming task so that it executes in parallel internally using any
of the patterns that are described in this book.
The Adatum Dashboard
example doesn’t offer much opportunity for breaking down the slowest
tasks into additional tasks that execute in parallel. This is because
the paths are linear. However, you can use the Parallel Loops and
Parallel Aggregation patterns to exploit more of the potential
parallelism within each of the Analyze tasks if they take the most time. The task graph remains unchanged, but the tasks within it are now also parallelized.
5. Modifying the Graph At Run Time
The code in the
financial program’s analysis engine creates a static task graph. In
other words, the graph of task dependencies is reflected directly in the
code. By reading the implementation of the analysis engine, you can
determine that there are a fixed number of tasks with a fixed set of
dependencies among them.
The extension of the
analysis tasks in the UI layer is an example of dynamic task creation.
The UI augments the graph of tasks by adding continuation tasks
programmatically, outside of the context where these tasks were
originally created.
Dynamically
created tasks are also a way to structure algorithms used for sorting,
searching, and graph traversal.