Delphi Prism
Delphi Prism is de nieuwe .NET variant van Delphi, en bestaat uit drie onderdelen. Allereerst is daar de Oxygene compiler, gemaakt en onderhouden door RemObjects Software (destijds uitgebracht onder de naam Chrome). Deze Oxygene compileer draait als een plug-in in de Microsoft Visual Studio IDE (de gratis Shell of Express editie is voldoende), en levert daarmee als tweede onderdeel alle designers voor WinForms, WPF, ASP.NET, Silverlight, etc. Behalve een Compact Framework designer dan, omdat die nog steeds vastgebakken zit aan C# of VB. Het derde onderdeel van Dephi for .NET is het enige deel dat nog van CodeGear afkomstig is: de database connectivity in de vorm van de dbExpress drivers en DataSnap mogelijkheden. Op dit moment beperkt tot InterBase en BlackfishSQL voor dbExpress drivers, en alleen nog DataSnap clients, maar in toekomstige uitbreidingen kunnen we extra dbExpress drivers en DataSnap server functionaliteit verwachten.
De database connectivity is het enige onderdeel van Dephi for .NET dat nog van CodeGear afkomstig is
Async
De Oxygene compiler van Delphi Prism ondersteunt een taal die het async keyword bevat. Dit keyword kan gebruikt worden om bij een methode aan te geven dat deze asynchroon moet worden uitgevoerd; in een aparte thread dus. De declaratie van een asynchrone method kan als volgt zijn:
method DoeIetsMetInputParameters(x,y,z: Integer); async;
Let op dat we in Delphi Prism geen procedure of function meer hoeven te schrijven, maar het generiekere method-keyword kunnen gebruiken. En voor een asynchrone methode voegen we het keyword async toe aan het eind van de declaratie.
Zoals de naam van de method al doet vermoeden, heeft een async method een voordeel maar ook een aantal beperkingen. Het grote voordeel is uiteraard dat de main thread meteen terugkeert na de aanroep van de async method. De async method zal in een aparte thread uitgevoerd worden, waardoor de main thread meteen verder kan. Nadeel daarbij is dat de main thread niet zal weten wanneer de async method is afgelopen. Daarnaast kan de method alleen maar input parameters bevatten, en geen resultaat terugeven omdat deze natuurlijk pas bekend worden tijdens of na afloop van het uitvoeren van de async method. En de main thread die de async method aanroept zou niet kunnen weten wanneer het resultaat daadwerkelijke beschikbaar is.
Dit leidt al snel tot de vraag waar en wanneer een async method dan nuttig kan zijn. In praktijk gebruik ik het veel in situaties waar je iets wilt doen dat verder zonder interactie kan worden afgewerkt. Het genereren en/of printen van een report bijvoorbeeld, of het versturen van een e-mail. Dan is het ideaal dat de aanroep van de async method geen verdere tijd in beslag neemt en de main thread gewoon doorgaat.
Async Code
Behalve async methods kunnen we het async keyword ook gebruiken om van blokken code aan te geven dat deze asynchroon (in een aparte thread) moet worden uitgevoerd. Dit kan bijvoorbeeld als volgt:
class method ConsoleApp.Main;
var
x: Integer;
begin
x := 42;
async begin
x := x + x;
Console.WriteLine('x = ' + x.ToString);
end;
x := x / 2;
Console.WriteLine('x = ' + x.ToString);
Console.ReadKey
end;
Ook hierbij geldt dat het async code block geen persistente wijzigingen kan aanbrengen in lokale variabelen. In bovenstaand voorbeeld wordt de variabele X echter wel aangepast. Om hiermee (concurrency) problemen te voorkomen wordt, voordat het async code block start, eerst de gehele context gekopieerd. In dit geval is dat alleen de variabele X, maar dat kan in de praktijk heel wat meer zijn.
De output van het stuk code is dan ook x = 84, en x = 21. Misschien in deze volgorde, of wellicht andersom (de x = 21 eerst). Ze kunnen in theorie zelfs door elkaar geschreven staan, maar dat heb ik zelf nog niet kunnen reproduceren.
Behalve het feit dat de variabele x in het async code block niet de echte x is, maar een kopie, is het ook niet gegarandeerd wanneer het async code block klaar is. Maar daar is wel een oplossing voor: we kunnen het gehele async code block toekennen aan een System.Action variabele en die gebruiken om op een bepaald moment deze variabele als statement aan te roepen waarna – indien het async code block nog niet klaar is – gewacht wordt tot deze thread afgerond is. Dat gaat dan als volgt:
class method ConsoleApp.Main;
var
x: Integer;
begin
x := 42;
var xtask := async begin
x := x + x;
Console.WriteLine('x = ' + x.ToString); // 21
end;
x := x / 2;
Console.WriteLine('x = ' + x.ToString); // 84
xtask(); // wachten tot de thread klaar is
Console.ReadKey
end;
De aanroep van xtask() zal er nu voor zorgen dat er op dat moment gewacht wordt – indien nodig – tot het async code block dat we aan xtask hebben toegewezen is uitgevoerd. Die truc kunnen we helaas niet uithalen met async methods, maar alleen met async code blocks.
Futures
Nadeel van een async code block blijft dat je wel iets kunt doen of uitrekenen, maar het resultaat niet meer kunt “terugzetten” naar de main thread. Het async code block wordt als het ware in een eigen wereldje uitgevoerd, met een kopie van de omgeving.
We maken gebruik van een andere Delphi Prism feature: het future keyword
Als we wel nog iets terug willen geven, maar ook gebruik willen maken van de kracht van asynchrone code, dan moeten we een andere Delphi Prism feature gebruiken: het future keyword. Door gebruik te maken van dit keyword geven we aan dat een variabele een waarde heeft die op dit moment nog niet bekend is (bijvoorbeeld omdat de toekomstige waarde ervan in een async code block of method wordt berekend), maar op het moment dat we hem nodig hebben, zal de waarde er zijn (of blijven we erop wachten).
Stel bijvoorbeeld dat we voor een hypotheekberekening twee maandbedragen bij elkaar moeten optellen: het rente bedrag en het premie bedrag. Beide bedragen zijn afkomstig uit een wellicht complexe berekening, en het zou zonde zijn om die op elkaar te laten wachten. Zeker als je een dual-core of multi-processor machine hebt en een deel ervan toch niks staat te doen.
De syntax van het gebruik van futures voor dit voorbeeld is als volgt:
method MainForm.BerekenMaandbedrag: Double;
var
RenteBedrag: future Double;
PremieBedrag: future Double;
VerborgenKosten: Double;
begin
RenteBedrag := async BerekenRenteBedrag;
PremieBedrag := async BerekenPremieBedrag;
VerborgenKosten := BerekenVerborgenKosten;
Result := RenteBedrag + PremieBedrag + VerborgenKosten
end;
Zowel de waarde van RenteBedrag als die van PremieBedrag worden in aan aparte thread berekend. Door dat op deze manier te doen zijn de waardes waarschijnlijk nog niet bekend als we aan de verborgen kosten berekening gaan beginnen. Maar dat maakt niet uit, want op het moment dat we alles nodig hebben (bij het Result statement) zal blijken of de async code al klaar is, en de future variabelen hun waarde al hebben, of dat we daar nog even op moeten wachten. Je zou in de code eventueel een soort indicator op het scherm aan kunnen zetten (“waiting…”) voordat je de waarde van een future variabele ophaalt (of erop blijft wachten), zodat de gebruiker in ieder geval door heeft wat er aan de hand is als we hier enige tijd moeten wachten tot de waarde van de future bekend is.
In feite is de xtask variabele die we eerder gebruikte om op een async code block te wachten ook een future. En het “aanroepen” van de variabele is niets anders dan het gebruiken van de waarde, waardoor we blijven wachten indien deze (het resultaat van het code block) nog niet bekend is.
PFX Framework: Threads vs. Tasks
De async voorbeelden runnen in een eigen thread, in de .NET thread pool. Echter, de volgende versie van het .NET Framework bevat een PFX Framework voor Parallele Extensies, en als we dat erbij gebruiker zal de async method of code als een echte parallele task draaien in plaats van alleen maar als een nieuwe thread in the thread pool.
Parallel
Een nieuwe feature die verbonden is aan het PFX Framework is het parallel keyword. Hiermee kunnen we aangeven dat een stuk code parallel uitgevoerd moet worden, b.v. een for-loop die in stukken geknipt zal worden.
method MainForm.Doe;
begin
for parallel i: Integer := 0 to 10 do
begin
BerekenIetsMetI(i);
end;
end;
Deze code zal alleen compileren als het PFX wordt aangetroffen (de benodigde assemblies zijn nodig), en zal ook alleen draaien op machines waar de PFX aanwezig is.
Meer experimenten met PFX en het parallel keyword (waarin we zullen zien hoe de loop in stukken wordt geknipt, en wat er met de i gebeurt) zullen volgen als ook de ontwikkeling aan .NET 4.0 en PFX wat meer gevorderd is. Het goede nieuws is dat Delphi Prism nu al deze komende features ondersteunt.
Conclusie
In dit artikel heb ik laten zien hoe enkele taalelementen van Delphi Prism werken – taalelementen zoals async en futures die overigens al enige tijd in de Oxygene compiler zaten, dus niet zo heel verschrikkelijk nieuw zijn. Het leuke is in ieder geval dat Delphi for .NET niet langer “achterloopt” op het gebied van .NET features, maar juist nu al gebruik maakt van functionaliteit die nog niet eens af is of volledig beschikbaar.
Wie nog vragen of opmerkingen heeft, of Delphi Prism zelf een keer wil proberen, kan me altijd per e-mail bereiken op info@eBob42.com.