Synchronous and Asynchronous Operations

<< Click to Display Table of Contents >>

Navigation:  Developer's Guide > Best Practices > N-Tier web applications >

Synchronous and Asynchronous Operations

By default, uniGUI applications are standard web applications working in asynchronous mode. It means that the server doesn't need to block a session thread while waiting for a user response coming from the client.

 

Asynchronous Mode

 

How to use ShowModal in this mode?

 

Let's use a very simple example. We will create a form for entering a person name with buttons for accepting or canceling the request. Another form will use the previous form for updating its own internal field FFullName.

 

unit Unit3;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics,
  Controls, Forms, uniGUITypes, uniGUIAbstractClasses,
  uniGUIClasses, uniGUIForm, uniButton, uniEdit, uniGUIBaseClasses, uniLabel;
 
type
  TUniForm3 = class(TUniForm)
    UniLabel1: TUniLabel;
    edtFullName: TUniEdit;
    btnOk: TUniButton;
    btnCancel: TUniButton;
  private
    function GetFullName: string;
    { Private declarations }
  public
    { Public declarations }
 
    property FullName : string read GetFullName;
  end;
 
function UniForm3: TUniForm3;
 
implementation
 
{$R *.dfm}
 
uses
  MainModule, uniGUIApplication;
 
function UniForm3: TUniForm3;
begin
  Result := TUniForm3(UniMainModule.GetFormInstance(TUniForm3));
end;
 
{ TUniForm3 }
 
function TUniForm3.GetFullName: string;
begin
  if edtFullName.Text = '' then
    Result := 'John Doe'
  else
    Result := edtFullName.Text;
end;
 
end.

 

This form will return the entered full name or 'John Doe' if the full name was empty. Its result will depend on pressing the button OK or Cancel (or closing the form). How we use it?

 

unit Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics,
  Controls, Forms, uniGUITypes, uniGUIAbstractClasses,
  uniGUIClasses, uniGUIForm, uniGUIBaseClasses, uniButton;
 
type
  TUniForm1 = class(TUniForm)
    btnGetFullName: TUniButton;
    procedure btnGetFullNameClick(Sender: TObject);
  private
    { Private declarations }
 
    FFullName : string;
  public
    { Public declarations }
  end;
 
function UniForm1: TUniForm1;
 
implementation
 
{$R *.dfm}
 
uses
  MainModule, uniGUIApplication,
  Unit3;
 
function UniForm1: TUniForm1;
begin
  Result := TUniForm1(UniMainModule.GetFormInstance(TUniForm1));
end;
 
procedure TUniForm1.btnGetFullNameClick(Sender: TObject);
begin
  UniForm3.ShowModal
  (
    procedure (Sender: TComponent; AResult: Integer)
    begin

      if TModalResult(AResult) = mrOk then
        FFullName := (Sender as TUniForm3).FullName;
    end
  );
end;
 
end.

 

What happens here?

 

We start by pressing the button GetFullName.

As soon as we call UniForm3, an instance of the form TUniForm3 will be created and returned.

The call to ShowModal will popup the form and it will behave like a client-side modal form (meaning that no further interaction is allowed outside of this form).

However, server-side, the program will continue, which in this case means that it will exit the OnClick event handler.

At this point, the server finished the previous request (that is, the OnClick event triggered client-side by the user).

When the user finally clicks any of the buttons (Ok or Cancel) or close the form, another request will go to the server: handle the ShowModal result for that form.

The server will identify the user session, and will execute the anonymous method which still keeps the reference to UniForm3 (standard behavior for anonymous methods).

Once the anonymous method is executed, the form UniForm3 will be available for disposal (its lifetime being managed by the uniGUI framework).

 

Synchronous Mode

 

It is always preferred to create the web application in asynchronous mode, but when migrating old VCL applications, the developer could find scenarios which couldn't be implemented or that will make the task too difficult. If necessary, uniGUI can be forced to work as a typical VCL application.

 

If the developer needs to reproduce the synchronous mode available in VCL applications, the property MainModule.EnableSynchronousOperations must be set to true.

 

ShowModal

 

How to use ShowModal in this mode?

 

procedure TUniForm1.btnGetFullNameClick(Sender: TObject);
begin
  if UniForm3.ShowModal = mrOk then
    FFullName := UniForm3.FullName;
end;
 

This is the same code we typically use for VCL applications. It also stops the execution after calling ShowModal until a result is returned. This kind of code requires spawning a thread in the server which will be waiting for the client request/answer. As you can expect, this kind of applications consumes more resources than a standard web application. Fortunately, uniGUI uses an internal thread-pool to minimize thread usage, but for each waiting modal window a thread will be consumed. After the modal window is closed, the waiting threads will be returned to the thread pool for future usage.

 

In addition to ShowModal, uniGUI allows to execute synchronous operations in other scenarios. Let's examine some demos.

 

Blocking Modals

 

There is one demo illustrating blocking modals, "BlockingModals".

 

BlockingModals\

 

The next code shows how each function is now a blocking call like it was in VCL.

 

procedure TMainForm.btnShowModalClick(Sender: TObject);
var
  M : TModalResult;
begin
  M := UniForm1.ShowModal;
 
  case M of
    mrOK  : ShowMessage('OK');
    mrCancel  : ShowMessage('Cancel');
    mrNone  : ShowMessage('None');
  end;
 
  ShowMessage('Done, ' + UniForm1.UniEdit1.Text);
end;
 
procedure TMainForm.btnMessageDlgClick(Sender: TObject);
var
  Res : Integer;
begin
  Res :=  MessageDlg('Test', mtConfirmation, mbYesNoCancel);
 
  case Res of
    mrYes  : ShowMessage('Yes');
    mrNo  : ShowMessage('No');
    mrCancel  : ShowMessage('Cancel');
  end;
end;
 
procedure TMainForm.btnPromptClick(Sender: TObject);
var
  sResult : string;
begin
  if Prompt('Please type something?''Value',
       mtInformation, mbYesNoCancel, sResult, True) = mrYes then
    ShowMessage('Result: ' + sResult);
end;
 
procedure TMainForm.btnNestedCallsClick(Sender: TObject);
var
  sResult : string;
begin
  if UniForm1.ShowModal = mrOK then
    if Prompt('Please type something?''',
         mtInformation, mbYesNoCancel, sResult, True) = mrYes then
      if MessageDlg('Continue?', mtConfirmation, mbYesNoCancel) = mrYes then
        ShowMessage('Result: ' + sResult + ' ' + UniForm1.UniEdit1.Text );
end;
 
procedure TMainForm.btnNestedForms1Click(Sender: TObject);
begin
  if UniForm1.ShowModal = mrOK then
    if UniForm2.ShowModal = mrOK then
      if UniForm3.ShowModal = mrOK then
        if UniForm4.ShowModal = mrOK then
        begin
          ShowMessage('All shown!');
        end;
end;
 
procedure TMainForm.btnNestedForms2Click(Sender: TObject);
begin
  if UniForm5.ShowModal() = mrOK then
    ShowMessage('Completed');
end;
 
procedure TMainForm.btnFileUploadClick(Sender: TObject);
begin
  if UniFileUpload1.Execute then
  begin
    ShowMessage('File upload completed.' +
                ^M^M'Filename: ' + UniFileUpload1.FileName +
                ^M^M'Temporary file is located under:' +
                UniFileUpload1.CacheFile);
  end;
end;

 

Progress Bar

 

Demo "SyncClientUpdate-1" shows how to update a progress bar while executing a lengthy task.

 

SyncClientUpdate-1

 

Once the user clicks the button Start, this is the kind of code achieving the desired behavior:

 

begin
  FCancelled := False;
 
  UniProgressBar1.Min := 1;
  UniProgressBar1.Max := MAX_FILES;
  btnStart.Enabled  := False;
  btnCancel.Enabled := True;

 

  // Reset Progressbar and Label
  UpdateClient(0);
 

  // Initial refresh before entering the loop.

  // (Progressbar and Label will be refreshed)
  UniSession.Synchronize;
 
  N := 0;
  try
    for I := 1 to MAX_FILES do
    begin

      // Refresh the client at "X_INTERVAL" intervals
      if UniSession.Synchronize(X_INTERVAL) then
        UpdateClient(N);
 

      // Check if operation is cancelled.

      // (Either when Cancel button is pressed or Form is closed)
      if FCancelled then Break;
 
      // perform some tasks here
      CreateDummyFile(I);
 
      Inc(N);   // +1 number of created files
    end;
 
    UpdateClient(N);
  finally
    btnStart.Enabled  := True;
    btnCancel.Enabled := False;
  end;
end;

 

The call UniSession.Synchronize forces an immediate update. Passing an argument allows to synchronize at a controlled period (every some milliseconds).

 

Synchronous processing using ShowMask

 

Demo "SyncClientUpdate-3" executes a sequence of tasks with status updates and using ShowMask for avoiding user interaction with other visual controls.

 

SyncClientUpdate-3

 

The code for the Start button is:

 

begin
  UniLabel1.Caption := '';
  UniLabel2.Caption := '';
  UniLabel3.Caption := '';
  UniCheckBox1.Enabled := False;
 
  UniImage1.Hide;
 
  UniProgressBar1.Position := 0;
  UniButton1.Enabled := False;
 
  ShowMask('Level 1 in progress ..');
  UniSession.Synchronize;
 
  Sleep(1000);
 
  UniProgressBar1.Position := 1;
  UniLabel1.Caption := 'Level 1 Completed';
  UniLabel1.Font.Style := [fsBold];
  HideMask;

 
  ShowMask('Level 2 in progress ....');
  UniSession.Synchronize;
 
  Sleep(1000);
 
  UniProgressBar1.Position := 2;
  UniLabel2.Caption := 'Level 2 Completed';
  UniLabel2.Font.Style := [fsBold];
  HideMask;

 
  ShowMask('Level 3 in progress ......');
  UniSession.Synchronize;
 
  Sleep(1000);
 
  UniProgressBar1.Position := 3;
  UniLabel3.Caption := 'Level 3 Completed';
  UniLabel3.Font.Style := [fsBold];
  HideMask;

 
  ShowMask('Last level in progress ........');
  UniSession.Synchronize;
 
  Sleep(1000);
 
  UniProgressBar1.Position := 4;
  UniImage1.Show;
  UniButton1.Enabled := True;
  UniCheckBox1.Enabled := True;
  HideMask;
end;

 

Synchronize waiting for events or timeout

 

Demo "SyncClientUpdate-5" shows the difference between Synchronize(Wait : boolean) and Synchronize(Delay : integer).

 

SyncClientUpdate-5

 

Pressing the button "Start No Wait" will move the UniPanel1 to the bottom right corner 5 pixels every 200 milliseconds (showing the movement).

 

Pressing the button "Start" will accomplish the same, but each step will happen only when some client-side event is detected (for example, pressing the dummy button "Press!" or resizing the form.

 

The code is:

 

procedure TMainForm.btnStartNoWaitClick(Sender: TObject);
begin
  btnStart.Enabled := False;
  btnStartNoWait.Enabled := False;
 
  while UniPanel1.Top < Self.Height - 150 do
  begin
    if UniSession.Synchronize(200then // wait 200 ms
    begin
      UniPanel1.Top := UniPanel1.Top + 5;
      UniPanel1.Left := UniPanel1.Left + 5;
    end
    else
      Sleep(10);
  end;
 
  UniPanel1.Top := 5;
  UniPanel1.Left := 5;
  btnStart.Enabled := True;
  btnStartNoWait.Enabled := True;
end;
 
procedure TMainForm.btnStartClick(Sender: TObject);
begin
  FStopped := False;
 
  btnStart.Enabled := False;
  btnPress.Enabled := True;
  btnStop.Enabled := True;
 
  while not FStopped do
  begin
    UniSession.Synchronize(True); // wait until next event
 
    UniPanel1.Top  := UniPanel1.Top + 5;
    UniPanel1.Left := UniPanel1.Left + 5;
  end;
 
  UniPanel1.Top := 5;
  UniPanel1.Left := 5;
  btnStart.Enabled := True;
  btnPress.Enabled := False;
  btnStop.Enabled := False;
end;
 
procedure TMainForm.btnPressClick(Sender: TObject);
begin
// Dummy Event generator
end;