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.