Yesterday I wrote about futures in OmniThreadLibrary 2.0 (supported only in D2009+) and I mentioned that implementing futures in plain D2009+ should be really simple. And it really is – it took me all of 15 minutes to write the supporting library and a simple test case.
The code below is released to public domain. I’m claiming no copyrights – use it as you wish. You don’t even have to attribute it to me. Just don’t use it for evil purposes ;)
unit DelphiFuture; interface uses Classes; type IFuture<T> = interface function Value: T; end; TFutureDelegate<T> = reference to function: T; TFutureThread<T> = class(TThread) strict private FAction: TFutureDelegate<T>; FResult: T; public constructor Create(action: TFutureDelegate<T>); procedure Execute; override; property Result: T read FResult; end; TFuture<T> = class(TInterfacedObject, IFuture<T>) strict private FResult: T; FWorker: TFutureThread<T>; public constructor Create(action: TFutureDelegate<T>); function Value: T; end; implementation uses SysUtils; { TFutureThread<T> } constructor TFutureThread<T>.Create(action: TFutureDelegate<T>); begin inherited Create(false); FAction := action; end; procedure TFutureThread<T>.Execute; begin FResult := FAction(); end; { TFuture<T> } constructor TFuture<T>.Create(action: TFutureDelegate<T>); begin inherited Create; FWorker := TFutureThread<T>.Create(action); end; function TFuture<T>.Value: T; begin if assigned(FWorker) then begin FWorker.WaitFor; FResult := FWorker.Result; FreeAndNil(FWorker); end; Result := FResult; end; end.I’ve used my usual test case, calculating number of primes between 1 and 1.000.000.
implementation uses DelphiFuture; function IsPrime(i: integer): boolean; var j: integer; begin Result := false; if i <= 0 then Exit; for j := 2 to Round(Sqrt(i)) do if (i mod j) = 0 then Exit; Result := true; end; procedure TForm1.btnTestClick(Sender: TObject); var numPrimes: IFuture<integer>; begin numPrimes := TFuture<integer>.Create(function: integer var iPrime: integer; begin Result := 0; for iPrime := 1 to 1000000 do if IsPrime(iPrime) then Inc(Result); end ); lbLog.Items.Add(Format('%d primes from 1 to 1000000', [numPrimes.Value])); end;
Is it not possible/preferable to make TFutureDelegate a reference to a named function. You then create a function called, say CalculatePrime and set up the future thus :
ReplyDeletenumPrimes := TFuture.Create(CalculatePrime);
I just think the use of anonymous methods here can be confusing. btw, I haven't tried this, it's just a thought.
TFuture.Create(function: T) should be possible, true.
ReplyDeleteHi Primoz,
ReplyDeleteI'm not familiar with the threading terminology of "futures". The only reference via Google that I can find was http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/Future.html. Can you either explain what futures are for us newbies or possibly point us to somewhere on the web for more details. I am particularly interested in knowing under what circumstances would you use futures and what problems do they solve. Thanks in advance and for further extending my curiosity of Delphi threading.
Colin:
ReplyDeleteA future is a function that calculates a value that doesn't need to be ready immediately.
Most of the time, when you call a function, the flow of control transfers to it until the function returns. You could look at it as if the function call blocks the current procedure until it's finished.
A future is basically a non-blocking function call. You create it and it starts to run in a separate thread, while you do other things in the current function. Then later on, you ask for the value of the future, which it's been calculating all this time. If it's not done, it will block at that point until it's finished, otherwise it gives you the value immediately.
For example, if you need to calculate two different values and add them together, and they're both rather involved calculations, you can put one of them in a future and have it run in a different thread and on a different processor while you're calculating the other one. Then, instead of taking X + Y amount of time to do the whole calculation, it takes X or Y, whichever is longer. If X and Y are close to equal, then you've cut your processing time in half.
Hi Mason,
ReplyDeleteThanks for the explanation! I owe quite a bit of my understanding of all things new (and sometimes old) in Delphi to your replies in stackoverflow. I'm re-reading Primoz's posts and now it's beginning to make a bit of sense now (Not because it was poorly worded but because of my previous lack of understanding!).
Now if I can just get my head around the rest of the OTL... :)
@Mason: Thanks for the detailed answer.
ReplyDelete@Colin: More info on futures in Task Parallel Library: http://www.devx.com/dotnet/Article/39204/1763/page/5. There's also a longer description at Wikipedia: http://en.wikipedia.org/wiki/Futures_and_promises.
The implementation above is just a proof of concept and doesn't implement advanced features from the Java implementation. The OTL version will have more functionality.
BTW, if anybody can find a Future definition on the MSDN, can she/he please post the link here? If I try to search for "Future", Microsoft's searcher just barfs a page of errors back to me :(
ReplyDeleteThanks Primoz!
ReplyDeleteA little (and dirty) improvement:
type
IFuture = interface
function Value: T;
function Available: boolean; //added method
end;
//implementation
function TFuture.Available: boolean;
begin
Result := WaitForSingleObject(FWorker.Handle, 1) <> WAIT_TIMEOUT; //could be more robust
end;
so you can check the availability of the result
if not numPrimes1.Available then
begin
ListBox1.Items.Add('Still not available');
Exit;
end
else
ListBox1.Items.Add(Format('%d primes from 1 to 1000000',
[numPrimes1.Value]));
Something similar will appear in the OTL version: Cancel, IsCanceled and IsDone.
ReplyDelete@Babnik: It seems that function and methods are automatically upgraded to delegates. (You can pass a "function: integer" or "function: integer of object" to a method that expects "reference to function: integer" and everything will work correctly.) So there's no need to enhance the base class, it should already accept such functions/methods. (I've tested this with the OTL version and everything works just fine.)
ReplyDeleteBTW, does anybody know if this behaviour is documented?
Also missing from the code above:
ReplyDeletedestructor TFuture.Destroy;
begin
FreeAndNil(FWorker);
inherited;
end;
Without that, the code will leak if a future is released before the Value function is called.
@gabr - http://docwiki.embarcadero.com/RADStudio/en/Anonymous_Methods_in_Delphi explicitly says methods are directly assignable to 'reference to' type variables. While only on blogs, Barry Kelly has also said in the past that assignment compatibility with both method and standalone routines was intentional (e.g. see his comment at http://sergworks.wordpress.com/2010/01/27/anonimous-methods-in-delphi-the-internals/#comment-5).
ReplyDelete@Chris: Thanks for the links!
ReplyDeleteHello. Its very interesting code for me.
ReplyDeletePrimož please explain why you used interfaces
type
IFuture = interface
function Value: T;
end;
?
Why dont simply make "TFuture=class ...end" and free it when job is done?
numPrimes := TFuture.Create(function: integer
var
iPrime: integer;
begin
Result := 0;
for iPrime := 1 to 1000000 do
if IsPrime(iPrime) then
Inc(Result);
end
);
try
lbLog.Items.Add(Format('%d primes from 1 to 1000000',
[numPrimes.Value]));
Finally
numPrimes.free;
end;
I could not understand idea of using interfaces in Delphi.
For what are they used for?