Parallel Programming Notes - Futures
a future is a stand-in for a computational result that is initially unknown but becomes available at a later time
parallel tasks are asynchronous actions, futures are asynchronous functions
futures are implemented using Task<TResult> type
instead of explicitly waiting for task to complete using Wait method, simply ask for task's result when it is needed
if task has completed result is returned immediately
if task has not completed, thread is blocked until result is returned (while thread is blocked core is available for other tasks)
if task has not started it will be executed inline if possible
Continuation Tasks
a variation of Futures pattern is known as continuation tasks
tasks that automatically start when other tasks aka antecedents complete
many times antecedents consist of futures whose result values are used as inputs to the continuation task
continuation tasks represent the nested application of asynchronous functions
The Basics
sequential method
    var a = 22;

    var b = F1(a); 
    var c = F2(a); 
    var d = F3(c); 
    var f = F4(b, d); 
    return f;
methods F1, F2, F3 & F4 are processor intensive
task graph to for calculating f
         b = F1(a) 
    a ->                         -> f = F4(b, d)
         c = F2(a) -> d = F3(c) 
    
either path can run as a future
    var a = 22;

    var bf = Task<int>.Factory.StartNew(() => F1(a));
    var c = F2(a);
    var d = F3(c);
    var f = F4(bf.Result, d);
    return f;
or
    var a = 22;

    var df = Task<int>.Factory.StartNew(() => F3(F2(a)));
    var b = F1(a);
    var f = F4(b, df.Result);
    return f;
exceptions that occur in a future are deferred & rethrown when the Result property is examined
    var a = 22;

    Task<int> futureD = Task<int>.Factory.StartNew(() => F3(F2(a)));
    try
    {
        int b = F1(a);
        int f = F4(b, futureD.Result);
        return f;
    }
    catch // any exception
    {
        Console.WriteLine("Saw exception");
        return -1;
    }
    
Continuation Tasks
continuation tasks make dependencies among futures obvious to the run-time doing the scheduling
example updating a UI
    var a = 22; 
            
    var futureB = Task.Factory.StartNew<int>(() => F1(a));
    var futureD = Task.Factory.StartNew<int>(() => F3(F2(a)));

    var futureF = Task.Factory.ContinueWhenAll <int, int>(new[] { futureB, futureD }, (tasks) => F4(futureB.Result, futureD.Result));
  
    futureF.ContinueWith((t) => myTextBox.Dispatcher.Invoke((Action)(() => { myTextBox.Text = t.Result.ToString(); })));
the ContinueWith method creates a continuation task with a single antecedent
the ContinueWithAll method creates a continuation task that can have multiple antecedents
Canceling Futures & Continuation Tasks
can handle cancellation entirely within a task
can pass cancellation tokens when tasks are created
Continue When "At Least One" Antecedent Completes
ContinueWhenAny method
Using .NET Asynchronous Calls with FUtures
tasks implement the IAsyncResult interface
supports the .NET Asynchronous Programming Model & the APM pattern
use TaskFactory object's FromAsync method to convert a pair of begin/end methods that use IAsyncResult into a task
futures easier to use than other implementations of IAsyncResult because how exceptions are handled
Removing Bottlenecks
a path is a sequence of tasks from beginning of work to the end result
a task graph can contain more that one task
duration of path is the sum of execution time for each task in a path
critical path is path with longest duration time
to make task graph run faster need to reduce duration of critical path
Modifying the Graph at Run Time
extension of tasks by adding continuation tasks programatically outside of the context of where the tasks were created
Design Notes
Decomposition into Futures and Continuation Tasks
problem space gets decomposed into operations with well-defined inputs and outputs
parallelism introduced by using futures and continuation tasks
Functional Style
both implicit & explicit approaches to synchronizing data between tasks
explicit when data is passed between tasks as parameters
implicit when using side-effects that modify shared data structures
explicit flow less prone to error
futures promote data isolation
Related Patterns
Pipelines Pattern
pipeline focuses on data flow by using queues instead of task dependencies
Master/Worker Pattern
tasks have child/parent relationship instead of antecedent/dependent
master task creates worker tasks, passes data to the tasks, and waits for result
internally parallel loops use this pattern
Dynamic Task Parallelism Pattern
creates tree of tasks on the fly in manner similar to recursion
if futures are asynchronous functions, dynamic task parallelism produces asynchronous recursive functions
Discrete Event Pattern
focuses on sending messages between tasks
no limit to number of events a task raises or when they are raised
back to index
n4jvp.com