Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / HPC / parallel-processing

Introduction to Delphi Prism

4.91/5 (13 votes)
18 Jun 2009CPOL5 min read 90.4K   328  
First steps tutorial for Delphi developers.

Image 1

Introduction

This article is for Delphi developers who want to learn Prism. It covers the most significant changes to the old Delphi language and the basics about the new parallel computing support.

Delphi Prism is a .NET language based upon Delphi. You might say it is the Delphi.NET. The name also stands for a Visual Studio extension that makes coding in Prism comfortable. It is a Visual Studio Shell application, so it integrates into an existing VS 2008 (if it is installed on the machine) or runs as a standalone IDE.

Why should you use Prism at all? First, there are Linux and Macintosh. Maybe you want to write platform independent code for Mono. Second, desktop applications are not always adequate and Delphi's ISAPI support is not state of the art. Third, you might be switching from native to managed development in general. Learning to use a new platform will be easier with a language you already know.

There's a fourth possible reason: Your applications are designed for multicore computers. Delphi Prism is designed especially for heavy multithreading and high responsiveness.

In this absolute beginner's tutorial, we'll write an address book using the Lucene.NET library. To introduce desktop and web at the same time, there'll be a WPF application to fill the search index and a web page to search it.

Index Builder: Your First WPF Project

Create a new Delphi WPF project. Visual Studio shows you the Forms Designer, where you can design the main dialog as usual. Change to the code editor. Delphi developers will be surprised: instead of a unit the code generator has declared a namespace. In the class definition, you find the new keywords partial and method. The latter replaces procedure as well as function, because .NET doesn't make a difference between those two. (Rather every method has a return value, for procedures it is void.) The name Create for constructors is replaced by nothing at all: objects get created with new, just as in the C-like languages.

namespace LuceneTest;

interface

uses
  System.Linq,
  System.Windows,
  System.Collections.ObjectModel,
  System.Collections.Generic,
  Lucene.Net.Index,
  Lucene.Net.Store,
  Lucene.Net.Search,
  Lucene.Net.Documents,
  Lucene.Net.Analysis.Standard,
  Lucene.Net.QueryParsers;

type
  Window1 = public partial class(System.Windows.Window)
  private
    method btnIndex_Click(sender: System.Object; e: RoutedEventArgs);
    method btnSearch_Click(sender: System.Object; e: RoutedEventArgs);
    method addDocument(name: String; address: String; writer: IndexWriter);
  public
    constructor;
  end;

Let's begin with the address book. A list of names has to be filtered and added to the search index. The new way to search lists looks quite confusing: the common Delphi developer is used to declare all variables above the procedure! But thanks to LINQ, there are lists without a defined type (that means, not defined in code), which don't even have to be declared.

method Window1.BuildSearchIndex;
var
  persons: Collection<person>;
  directory: Directory;
  writer: IndexWriter;
  analyzer: StandardAnalyzer;
begin
  persons := GetPersons;
  var filteredPersons := from n in persons
                       where (not n.Blocked)
                       order by n
                       select n;

  directory := FSDirectory.GetDirectory('L:\\Index', true);
  analyzer := new StandardAnalyzer();
  writer := new IndexWriter(directory, analyzer, true);

  for each person in IEnumerable<person>(filteredPersons) do
  begin
    addDocument(person.Name, person.Address, writer);
  end;

  writer.Optimize();
  writer.Close();
end;

Index Searcher: Your First Web Project

The newly built search index is to be searched by a web form. Create a new Delphi ASP.NET project! Again, Visual Studio shows you a designer window. Open the code behind to meet another new keyword. partial splits the Delphi class into an editable file Default.aspx.pas and the designer generated file Default.aspx.designer.pas. While you're typing ASP code, the designer fill its file with all necessary declarations. Your file will rarely be touched by the designer, unless the generated code must be edited by you. Now, double-click a button on the web form to insert an event handler.

By the way, this is a good point to introduce the double compare operator. You can make your code more readable using "0.2 <= score < 0.5" instead of the long version "0.2 <= score and score < 0.5".

method _Default.btnFind_Click(sender: Object; e: EventArgs);
var
  indexDirectory: String;
  parser: QueryParser;
  query: Query;
  searcher: IndexSearcher;
  searchResults: TopDocs;
  currentDocument: Document;
  lblResult: Label;
begin
  parser := new QueryParser('name', new StandardAnalyzer());
  query := parser.Parse(txtName.Text);
  indexDirectory := ConfigurationManager.AppSettings['LuceneIndexDirectory'];
  searcher := new IndexSearcher(FSDirectory.GetDirectory(indexDirectory, false));  
  searchResults := searcher.Search(query, nil, 10);

  for each currentResult in searchResults.scoreDocs do
  begin
    currentDocument := searcher.Reader.Document(currentResult.doc);
    lblResult := new Label();
    lblResult.Text := string.Format("{0} - {1}, {2}",
        ScoreToString(currentResult.score),
        currentDocument.Get('name'),
        currentDocument.Get('address'));
    pResult.Controls.Add(lblResult);
  end;
  pResult.Visible := true;
end;

method _Default.ScoreToString(score: Single): String;
begin
    Result := 'Impossible';
    if(0.2 <= score < 0.5)then
        Result := 'Improbable'
    else if(0.5 <= score < 0.8)then
        Result := 'Maybe'
    else if(0.8 <= score < 1.0)then
        Result := 'Quite sure'
    else
        Result := 'That is it!';
end;

More CPUs, Please!

Performance has always been an argument for using Delphi. Prism focuses on parallel programming and scores with handy new keywords which save lots of code and chaos.

The most handy one is async which simply declares a method as asynchronous. An asynchronous method is executed in a separate thread - automatically! That way a single word in the declaration line can prevent the main thread from being slowed down by log output calls. In the declaration of a variable, async is the promise that the variable will - in the future - contain a certain value. The value itself is calculated in a separate thread. If another thread accesses the variable before the calculation is finished, that thread is blocked until the value is available. But the most impressive news is the asynchronous block:

01 SomethingInTheMainThread(a);
02 var myBlock: future := async
03 begin
04    DoSomethingLong(b);
05    DoEvenMore(c);
06 end;
07 WholeLotInTheMainThread(d);
08 myBlock();

When is line 04 executed? Assuming there are enough processors, simultaneously with line 07. async makes the block run in another thread. The future variable myBlock is your handle for that thread, so you can wait for it in line 08. The method call myBlock() doesn't execute the block! Only if it hasn't yet finished, it waits for the thread to end.

Inside the asynchronous blocks, you may call other asynchronous methods. But be careful: the main thread doesn't wait for them. Waiting for an asynchronous block means waiting for this one thread, not for any "nested" threads that have been started by the block.

To visualise all that parallel stuff, the web page contains four checkboxes. They'll be evaluated in the background, when the user already sees the response page in his browser.

type
  _Default = public partial class(System.Web.UI.Page)
  protected 
      method btnFind_Click(sender: Object; e: EventArgs);
      method btnSurvey_Click(sender: Object; e: EventArgs);
  private
      logFile: String;
      logWriter: future StreamWriter := new StreamWriter(logFile);
      method ScoreToString(score: Single): String;
      method WriteLog(i: Integer; text: String); async;
  end;

implementation

method _Default.btnSurvey_Click(sender: Object; e: EventArgs);
begin
    logFile := ConfigurationManager.AppSettings['LogFile'];
    logWriter.AutoFlush := true;

    var backgroundWork: future := async
    begin
        logWriter.WriteLine("Loop starts at {0:mm:ss:fff}.", DateTime.Now);
        for each matching chkSurvey: CheckBox in pSurvey.Controls index i do
            if(chkSurvey.Checked)then
                WriteLog(i, chkSurvey.Text);

        logWriter.WriteLine("Loop stops at {0:mm:ss:fff}.", DateTime.Now);
    end;

    backgroundWork();
    logWriter.WriteLine("Page delivered at {0:mm:ss:fff}.", DateTime.Now);
end;

method _Default.WriteLog(i: Integer; text: String);
begin
    Thread.Sleep(1000);
    logWriter.WriteLine("{0}. Selection is '{1}' at {2:mm:ss:fff}.", i, text, DateTime.Now);
end;

The StreamWriter seems to be initialised with an empty path. But the keyword future causes the assignment in the declaration line to be executed shortly before the first access to the object. At that time, the path already is assigned. The method WriteLog is async, so it always runs in the background.

In the async block's thread, a for each matching loop iterates through all controls that are checkboxes. Every iteration can call another asynchronous method. When in the end the main thread waits for the block to finish, it waits only for the block's thread, not for any additional thread started by the block. The WriteLog calls can still run in the background, maybe waiting for a free CPU, when the user already sees the complete response.

The Source

To compile the demo applications, you need the assembly Lucene.Net.dll. If the latest stable release doesn't work, try version 2.0.004.

A 30 days trial version of Delphi Prism can be downloaded from Embarcadero. Sadly, they don't offer a free Express version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)