Futures
From The Oxygene Language Wiki
This is a Language topic
Feel free to add your notes to this topic below.
New page awaiting review
Please do not rely on the content. After review, the page will be indexed and may have its name changed.
If you have suggestions for this page, please send them to us: email.
Futures are a new language concept designed to facilitate Parallel Computing and developing applications that scale well on multi-core and multi-CPU systems.
A future is a strongly typed variable that represents a value that might or might not have been calculated yet, but is guaranteed to be (made) available when needed. Consider the following snippet of pseudo code that calculates the Sum of values in a binary tree:
method ThreeNode.Sum: Int32; begin var l: Int32 := Left.Sum; var r: Int32 := Right.Sum; result r+l; end;
This code first calculates the Sum of the left part of the subtree, then that of the right one. Finally, the two values are combined. Calculating the value of both "l" and "r" might take a relatively long time, yet the value of "l" is not actually needed until the very last line of the method. This is an ideal candidate for a future:
method ThreeNode.Sum: Int32; begin var l: future Int32 := async Left.Sum; var r: Int32 := Right.Sum; result r+l; end;
Instead of calculating the value of "l" in place as before, "l" is now defined as a future Int32, declaring that the variable does not actually hold the value of Left.Sum, but just the promise that, at some point in the future, it will. This first line of code will execute in virtually no time, and the method can move on to calculating "r", which is unchanged and will happen in line, as before.
Note how the value "l" has been changed to include the async keyword, making it an Async Statement. This will cause a separate thread (or a separate PFX Work Task) to be created to calculate the value for "l", asynchronously. While our main thread goes on to calculate "r", another thread (and hopefully another CPU or CU core) will get busy calculating "l" in parallel.
As we come to the last line of code, we simply use the + operator to add "r" and "l" together. Note how even though "l" was defined as a future Int32, is has all the characteristics of and can be used just like any normal Int32 variable.
Internally, the system can be in three different states when reaching the last line: (1) the background thread might be finished calculating "l", having the final value ready to be used, (2) the background thread might still be busy calculating "l" and (3) the background thread (or PFX Worker) might not even have started to work on calculating "l" – for example because all cores have been busy with other work.
It is important to realize that the first time a future is evaluated or accessed, it will automatically take care that each of the above three cases is handled appropriately. In the first case, it will simply return the readily-available value; in the second case, it will block the current thread until a value has been determined. Finally, in the last case, it will cancel any queued work task and – to avoid unnecessary threading – simply calculate the value in line.
Of course, the value for any given future will only be calculated once. After the initial access, any further use of the future will simply return the same value, without additional calculation overhead.
Types of Future
There are two types of future, asynchronous and synchronous. In contrast to the async future we have seen above, a synchronous future will not be calculated in a separate thread or work task, but will be evaluated in-line as part of the calling thread, the first time it is accessed.
From a functional perspective, both sync and async futures provide the exact same result, as far as program behavior is concerned; the only difference is whether the actual calculation happens in a separate thread.
A typical scenario for a sync future would be a delayed initialization of objects. For example, a field of a class might be defined as below, to defer the actual creation of the Costly object until the time it is actually needed.
type Cheap = public class private fCostly: future Costly := new Costly(); end;
Assuming the Costly class takes some time to initialize (perhaps because it fetches data from a remote server), the cost for this will still occur in-line of the main thread, but not as part of the initial creation of the Cheap class.
Syntax
Similar to Nullable Types, future types are declared by adding a prefix, future to the actual base type. A future type name can be used in any place where a type name is expected, including in variable and property declarations, method signatures, etc.:
var i: future Int32; var lPerson: future Person;
In addition, using an async expression with Type Inference will also automatically infer the appropriate future type:
var i := 1+2; // plain old Int32 var j := async 3+4; // future Int32, albeit a silly one
Also, assigning any expression to a variable of future type will automatically turn the expression into a future (as expected, assigning an async expression will turn it into an async future):
var i,j: future Int32; ... i := 1+2; // standard future; 1+2 will be evaluated when i is accessed i := async 1+2; // async future; 1+2 will be evaluated in background
It is worth mentioning that the async has a lower operator precedence than any other operator, meaning that any statement prefixed with async will be treated as such, as a whole.
It is also important to realize that the second statement below will not result in "j" being a future. What will happen is that an async future will be created for the expression "1+2", but that future will be evaluated right away, and have its result added with "3", to provide a normal Int32:
var i := async CalcThis() + 3; // reads as: async (CalcThis+5); var j := 3 + async 1+2() // will be evaluated right away!
You can also use the cast syntax to create a non-async future value and, for example, assign it to a local variable:
var i := future Integer(CalcThis());
Value-less Futures
As more of a variation of standard Asynchronous Statements, Delphi Prism introduces the concept of "typeless futures" to provide a waitable result on value-less async statements. Consider:
... var w: future := async begin ... // work in background thread done here ... end; ... // more work in main thread done here ... w(); // waits for the async block to finish
The call to "w();" does nothing more than wait for the asynchronous block to complete, if it hasn't yet.
Custom Future Implementation
When you add future use to your code two classes are automatically added to the resulting assembly that actually do all the background work to implement the future. These classes are FutureHelper and FutureHelper<T>. These classes are unavailable in the program code and cannot be called directly.
If for some reason you need custom implementation of Future support (for example when you need to use your own thread pool) you can create a class like
CustomFutureHelper = public class public class method IsDone<T>(aFuture: future T): Boolean; class method Execute<T>(aFuture: future T): future T; class method ExecuteAsync<T>(aMethod: future T; aWantResult: Boolean): future T; class method IsDone(aFuture: future): Boolean; class method Execute(aMethod: future): future; class method ExecuteAsync(aMethod: future; aWantResult: Boolean): future; end;
and register it via the project's properties (see Project Options (Build) for more details).
See Also
Area: Oxygene Language
Compiler version: Oxygene 5
Language Glossary — Keywords — Types — FAQ — How To