LabVIEW Craft
  • Blog
  • About
  • Contact
  • Tools
  • Resources

Futures, Promises and Continuations, Oh My!

12/12/2016

0 Comments

 
Owen Search
Delving into the world of asynchronous and concurrent programming can be quite daunting these days.  There is a deluge of jargon and hip new libraries to learn as a newbie.  At times, it feels like the difficulty in navigating these rough waters might rightly be interpreted as a dire warning: BEWARE THEE WHO DOES NOT KEEP HIS PROMISES.  But what is a promise anyway?  And for that matter, what is a future? Or a continuation? And here I thought I was in over my head when I started threading...
While I won't even try to completely illuminate these topics in a single blog post, I hope to provide a straightforward explanation that can help a beginner navigating the waters of asynchronous programming.  Really, once you get past the nomenclature, these programming constructs are REALLY interesting and useful.

Futures and Promises

Let's start with some simple definitions (in English!)
Future
  • A read-only data structure that represents a piece of information that is yet unavailable
  • Or, a placeholder for a piece of data that is not ready yet
  • Or, an order number at your local burger joint (which has yet to be called)
Promise​
  • A read/write data structure that represents a promise to fulfill a future value
  • Or, a placeholder for a task to produce a piece of data that is not ready yet
  • Or, the order ticket at your local burger joint, that the fry-cook fills with your food
So, futures and promises are very nearly the same thing.  You receive a future when you request a task to produce a yet unavailable piece of data, and you create a promise when you start that task and begin the required work.  When you complete the work, you fulfill the promise and thereby provide the future's value.  Here's a visual:
Picture
In this contrived example we can see the relationship between futures and promises.  Essentially the promise represents the producing end of the future/promise relationship, while the future represents the consuming end.  This explains why promises are read/write and futures are read-only.
Picture
The analogy of the burger joint works pretty well here.  Your order ticket represents a contract between you (the consumer of burgers) and the restaurant (the producer of burgers) where the producer promises to provide you a future burger after you have paid and placed your order.  While you wait for your order to be fulfilled you can go do other things like grab a soda.
Now that we have a common understanding of what constitutes a promise/future pair, their relevance to asynchronous programming becomes more apparent.  The promise/future pattern enables useful work to continue while some long-running task gets simultaneously executed.  Typically, the promise structure additionally allows exceptions to be passed back to the consuming task in the case of an error.
A common application of this pattern is in web applications.  Let's look at the process of requesting a web resource using both synchronous and asynchronous paradigms.
Picture
It's easy to see the benefit of the asynchronous approach (assuming we have safe mechanisms for dispatching the work).  In the procedural mindset, we must wait for our web-request to complete before continuing the flow of execution onto other, possibly independent tasks.  In the asynchronous mindset we start our long-running work on a concurrent task and continue doing useful things until we absolutely MUST wait for that web request to complete.  This benefit is especially compelling in UI development where you never want to cause the UI thread to hang or pause while the program does some long-running operation.
The future/promise pattern is a safe mechanism for enabling the above asynchronous programming paradigm.  Additionally, it provides a recognizable pattern for developers to implement concurrent processing, which might otherwise be risky from a complexity standpoint.

Continuations

Continuations are another related concept that tend to accompany futures and promises.  Again, let's start with a definition.

Continuation
  • An abstract representation of the control state of an application [Wikipedia]
  • Or, a task to be executed upon completion of some other (usually asynchronous) task
  • Or, for a more familiar concept, a callback function
Continuations seem much more ambiguous and vague then they really are.  Essentially a continuation is a definition of an operation or task that executes upon the completion of some other task.  Here are several easily identifiable programming constructs that can be classified as continuations:
  • An event-handling callback: establishes a continuation to be executed upon completion of the capture of its associated event
  • A catch statement: establishes a continuation to be executed upon the capture of a raised exception matching its catch condition
  • A finally statement: establishes a continuation to be executed upon the completion of a catch statement
If we think all the way back to C, we can see continuations implemented through function pointers and GOTO statements.  Continuations simply represent mechanisms for defining the flow of control within our application upon the completion of a particular task.

Implementation

Understanding what these terms mean is all well and good, but how are they implemented in practice?  Well the answer to that question really depends on the language and programming paradigm you are working within.  Some languages, like Java, C++ and Scala have direct implementations of the future/promise pattern described above.  C# on the other hand, has the concepts of async/await and Tasks to achieve the same effect.  In practice, if you can understand the fundamental concepts behind the above patterns, you can find a mechanism to implement them in your language of choice.  Heck, you can create your own future/promise pattern using threads, a few interfaces, and a single element queue.  That doesn't mean you should, though!
In C#, the async/await pattern with Tasks is really the way to implement this kind of programming paradigm.  Libraries supporting async tasks are fairly ubuquitous and, once you get it, it's pretty intuitive.  Let's introduce some new terms by mapping them to our previous discussion.

Task<T> and Task -> Provide our Future/Promise Data Structure
  • The Task<T> object is a data structure that represents some concurrent work; it is a contract that promises to return a value of type T sometime in the future, or return an error in the case of failure
  • The Task object is similar, except that it returns no value; it only promises to signify that some future work will eventually be completed or return an error (the void alternative to Task<T>)
Async -> Indicates a concurrent worker
  • Async marks a function capable of yielding the flow of control back to the caller (using the await keyword) while some long-running work is being performed
  • Async functions (generally) return a Task<T> or Task object, which is a container that will hold a future value or simply a promise to perform some future work
​Await -> Blocks until a future value is ready
  • Await is called on a Task object to indicate that it's result must be returned for the caller to continue doing it's job
  • If the result of the Task object is ready, it will be returned immediately and the caller's flow of control will continue as expected
  • If the result is not yet ready, the flow of control will be yielded to the calling function and the future result will be waited on
ContinueWith -> Create a Continuation to Follow Completion of a Task 
  • ContinueWith can be called on a task to define another task (a continuation) which is to be executed upon completion of the target task
This happens to be the mechanism for performing asynchronous work in C#/.NET and it is pretty intuitive when you get used to it.  Let's explore an example.
   1:  namespace AsyncDemo {
   2:   
   3:      // System
   4:      using System;
   5:      using System.Collections.Generic;
   6:      // Required for Task Parallel Library
   7:      using System.Threading.Tasks;
   8:   
   9:      /// <summary>
  10:      /// Demonstrates some asynchronous tasks
  11:      /// </summary>
  12:      class AsyncDemoProgram {
  13:   
  14:          /// <summary>
  15:          /// Synchronously run our asynchronous timestamp task
  16:          /// Notice in console output how task activities are interleaved
  17:          /// </summary>
  18:          /// <param name="args"></param>
  19:          static void Main(string[] args) {
  20:              PrintTimeStamps().Wait();
  21:              Console.ReadKey();
  22:          }
  23:   
  24:          /// <summary>
  25:          /// This task just runs until completion, it does not return any data to Main
  26:          /// </summary>
  27:          /// <returns></returns>
  28:          static async Task PrintTimeStamps() {
  29:              // Generate timestamps for 1 minute
  30:              var timeStampsTask = GenerateTimeStamps(new TimeSpan(0, 0, 10));
  31:              // Define a continuation to execute when we are done generating timestamps
  32:              // it will print a message
  33:              var timeStampsContinuation = timeStampsTask.ContinueWith(WriteMessage);
  34:              // Print a message now and then wait
  35:              Console.WriteLine("Hello World @ " + DateTime.Now);
  36:              // Asynchronously wait by directly awaiting the Task.Delay function
  37:              // If this task was called asynchronously the caller could continue execution here
  38:              await Task.Delay(5000);
  39:              // Print another message
  40:              Console.WriteLine("Hello World @ " + DateTime.Now);
  41:              // Wait for our timestamps to be returned
  42:              var timeStamps = await timeStampsTask;
  43:              // Print the timestamps from this task
  44:              foreach(DateTime time in timeStamps) {
  45:                  Console.WriteLine("Time Was Stamped @ " + time);
  46:                  await Task.Delay(200);
  47:              }
  48:          }
  49:   
  50:          /// <summary>
  51:          /// This task runs asychronously and generates a list of timestamps to pass back
  52:          /// to PrintTimeStamps
  53:          /// </summary>
  54:          /// <param name="lengthOfTime"></param>
  55:          /// <returns></returns>
  56:          static async Task<List<DateTime>> GenerateTimeStamps(TimeSpan lengthOfTime) {
  57:              var start = DateTime.Now;
  58:              var timeStamps = new List<DateTime>();
  59:              while(DateTime.Now-start < lengthOfTime) {
  60:                  // Wait for 1 second, letting the caller continue execution
  61:                  await Task.Delay(1000);
  62:                  // Create a new timestamp and print that we are working
  63:                  timeStamps.Add(DateTime.Now);
  64:                  Console.WriteLine("Stamping the Time @ " + DateTime.Now);
  65:              }
  66:              return timeStamps;
  67:          }
  68:   
  69:          /// <summary>
  70:          /// This continuation runs when the timestamp generation is complete
  71:          /// It is passed the result of GenerateTimeStamps
  72:          /// It will run synchronously because we never call await
  73:          /// </summary>
  74:          /// <param name="timeStamps"></param>
  75:          /// <returns></returns>
  76:          static async Task WriteMessage(Task<List<DateTime>> timeStamps) {
  77:              Console.WriteLine("All done generating timestamps @ " + DateTime.Now);
  78:          }
  79:   
  80:      }
  81:   
  82:  }

Let's break this down a bit.
  • Our primary worker functions are PrintTimeStamps, GenerateTimeStamps and WriteMessage
  • PrintTimeStamps is our primaryworker, it generates a list of timestamps by calling GenerateTimeStamps asynchronously.  It also sets up WriteMessage as a continuation for GenerateTimeStamps, so WriteMessage will run as soon as the timestamp generation completes
  • Everytime GenerateTimeStamps calls await Task.Delay, it is yielding control back to PrintTimeStamps
  • When PrintTimeStamps calls await Task.Delay or await timeStampsTask, it is yielding control back to Main (which happens to not matter, because we are running PrintTimeStamps synchronously from Main)
  • Main is synchronous, so it calls PrintTimeStamps synchronously by calling the Wait function of the Task that is returned

The output of this program looks like this.
Picture

As you can see, the output of PrintTimeStamps, GenerateTimeStamps and WriteMessage are interleaved as we would expect.  We await GenerateTimeStamps and not WriteMessage, so we can see the output of the continuation interleaved with the output of the final print loop in PrintTimeStamps.

Conclusion

I hope this helps clarify some asynchronous programming techniques and the concepts of futures, promises and continuations.  These programming constructs are really powerful if you can harness them for good use.  Tools like C#/.NET's async/await pattern allow us to leverage the power of the asynchronous programming paradigm without going down the multi-threading rabbit hole.  The level of abstraction provided allows the developer to focus on efficiently solving problems, and not on the overhead of low-level thread management.

Owen Search is an Avionics Test Software Engineer at SpaceX.  He is a Certified LabVIEW Architect, Certified TestStand Architect, Certified LabVIEW Embedded Systems Developer, Certified LabWindows/CVI Developer and NI Certified Professional Instructor.  C# is his current language of choice but he has a soft spot for C/C++ and a love of LabVIEW.  He holds a BS in Biomedical Engineering from the University of Connecticut.
0 Comments



Leave a Reply.

    RSS Feed

    Tags

    All
    Abstract Messaging
    Actor Framework
    Agile
    AI
    Asynchronous Programming
    Best Practices
    C#
    Complexity
    Continuations
    Control Values By Index
    Craftsmanship
    Dependency Inversion
    Dependency Viewer
    Futures/Promises
    Genetic Algorithm
    Liskov Substitution
    Malleable VIs
    Mediator Pattern
    .NET
    Object Oriented
    Object-Oriented
    Packed Project Library
    Parallelism
    Polymorphism
    Pub/Sub
    RawCap
    Root Loop
    Scrum
    Task Parallel Library
    TCP/IP
    TDD
    Test Engineering
    UML
    Unit Test
    VI Scripting
    VI Server
    WireShark

    Archives

    April 2019
    July 2018
    October 2017
    March 2017
    February 2017
    January 2017
    December 2016
    July 2016
    June 2016
    May 2016
    April 2016
    March 2016
    February 2016
    January 2016
    December 2015
    November 2015
    October 2015
    August 2015
    February 2015
    January 2015

    LabVIEW Blogs

    Eyes on VIs
    LabVIEW Hacker
    VI Shots
    LabVIEW Artisan
    Software Engineering for LabVIEW
    ​Wiresmith Techology
Picture
Powered by the Panda Ant

  • Blog
  • About
  • Contact
  • Tools
  • Resources