Synchronizing Between Processes

Recently I needed to use a semaphore to ensure that only one process at a time could perform a particular operation. A named Windows semaphore with a maximum count of one provides a simple way to coordinate access between processes.


Creating the Semaphore

First, add the required Windows unit and declare a field on the form to hold the semaphore handle:

uses
  Winapi.Windows;

interface

type
  TMainForm = class(TForm)
  private
    FSemaphore: THandle;
  end;

const
  // use a unique name
  SemaphoreName = 'MyAppSemaphore';

implementation

Create the semaphore when the form is created:

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FSemaphore := CreateSemaphore(nil, 1, 1, SemaphoreName);

  if FSemaphore = 0 then
    RaiseLastOSError;
end;

CreateSemaphore creates the named semaphore if it does not already exist. If a semaphore with the same name already exists, Windows returns a handle to the existing semaphore.

The initial and maximum counts are both set to 1, which means only one process can acquire the semaphore at a time.

Close the semaphore handle when the form is destroyed:

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  if FSemaphore <> 0 then
    CloseHandle(FSemaphore);
end;

Using the Semaphore

Before performing the protected operation, wait for the semaphore. Once it becomes available, perform the work and release it in a finally block:

if WaitForSingleObject(FSemaphore, INFINITE) = WAIT_OBJECT_0 then
begin
  try
    // perform the protected operation
  finally
    ReleaseSemaphore(FSemaphore, 1, nil);
  end;
end;

The finally block is important because it ensures that the semaphore is released even if the protected operation raises an exception.

If another process currently owns the semaphore, WaitForSingleObject waits until that process releases it. Using INFINITE means the call will wait for as long as necessary.

If you only want to wait for a maximum of 30 seconds, you can specify a timeout instead:

case WaitForSingleObject(FSemaphore, 30000) of
WAIT_OBJECT_0:
  begin
    try
      // perform the protected operation
    finally
      ReleaseSemaphore(FSemaphore, 1, nil);
    end;
  end;

WAIT_TIMEOUT:
  begin
    // the semaphore was not acquired within 30 seconds
  end;

WAIT_FAILED:
  RaiseLastOSError;
end;

This allows the application to handle a timeout rather than waiting indefinitely. The semaphore should only be released when WAIT_OBJECT_0 is returned.

WAIT_TIMEOUT does not mean that the wait operation failed. It means that the semaphore did not become available within the specified 30 seconds. This is an expected outcome when using a finite timeout and can be handled appropriately by the application.

WAIT_FAILED, on the other hand, means that the Windows wait operation itself failed, for example because an invalid handle was supplied. In this case, RaiseLastOSError can be used to raise an exception containing the corresponding Windows error.


Delphi's TSemaphore

The WinAPI approach above works well and makes the underlying mechanism explicit. Delphi also provides a TSemaphore class in System.SyncObjs, which provides a higher-level abstraction over semaphore synchronization.

The same named semaphore can be created using TSemaphore:

uses
  System.SyncObjs;

{ some procedure }

var
  Semaphore: TSemaphore;

Semaphore := TSemaphore.Create(nil, 1, 1, SemaphoreName);

try

  Semaphore.Acquire;
  try
    // perform the protected operation
  finally
    Semaphore.Release;
  end;

finally
  Semaphore.Free;
end;

This provides the same basic synchronization pattern while taking care of the underlying Windows handle through a Delphi object.