Lock pattern

Origin

This is a pattern developed by WhiteAnts and is based on Delphi’s update locking mechanism in the TStrings class.

Intent

Provide a mechanism to temporarily lock some aspects of a class.

Motivation

Often you’ll find that a object dispatches notifications as a result of changing it’s internal state. Clients will handle these notifications to synchronise with the object. If multiple changes are to be applied at once, this will result in multiple notifications and subsequent synchronisations. The lock pattern let’s you temporarily lock an aspect of the class, avoiding unneeded notifications. Locking can be applied nested.

Consider for example a TBag class which implements collection like behaviour. TBag dispatches an OnChange event each time something changes. If we wanted to add multiple objects to a bag, this would result in multiple change notifications. A GUI control wiring the OnChange event would have to be repainted each time, resulting in a poor performance. The lock pattern enables locking the bag before adding the objects. When the objects are added, the bag is unlocked again, which will result in a single dispatch of OnChange. The advantage of using a lock pattern rather than setting a Boolean flag, for example FUpdating, is that calls to Lock and Unlock can be nested.

Implementation

The implementation for the lock pattern applied to a class TBag is: (only pattern related code is shown)

type
  TBag = class (TObject)
  private
    FLockCnt: Integer;
  protected
    function Locked: Boolean;
    procedure SetLocking(Updating: Boolean);
  public
    procedure Lock;
    procedure UnLock;
  end;

implementation

procedure TBag.Lock;
begin
  Inc(FLockCnt);
  if FLockCnt = 1 then SetLocking(False);
end;

function TBag.Locked: Boolean;
begin
  Result := (FLockCnt <> 0);
end;

procedure TBag.SetLocking(Updating: Boolean);
begin
end;

procedure TBag.UnLock;
begin
  Dec(FLockCnt);
  if FLockCnt = 0 then SetLocking(True);
end;

In this implementation notice: The field FLockCnt which stores the state of the locking mechanism. FLockCnt = 0 represents an unlocked state. Any other value implicates a locked state. This allows nested calls to Lock and Unlock which are the only methods changing this field. The methods Lock and Unlock which provide the locking interface. Each time a call to one of these methods causes a locked state change, method SetLocking is called. Method SetLocking has one parameter Updating. If Updating is True, the bag has become unlocked due to a call to Unlock. If Updating is False, the bag has become locked due to a call to Lock. You would typically insert some code in this method which deals with the lock state change. Method Locked returns the state of the locking mechanism.

Notice how calls to Lock and Unlock always must be paired to avoid the Bag to remain locked forever. Therefore it is wise to use a try..finally block to make sure pairs are always matched, as is demonstrated in the following example.

A typical use of the lock mechanism would be:

procedure TBag.Add(Item: Pointer);
begin
  { Add Item to internal structure }
  Change;
end

procedure TBag.AddItems(Items: TList);
begin
  Lock;
  { Add multiple items }
  try
    for I := 0 to Items.Count - 1 do
      Add(Items[I]);
  finally
    { use try..finally to make sure Unlock is called }
    Unlock;
  end;
end;

procedure TBag.Change;
begin
  if not Locked then
    if Assigned(FOnChange) then FOnChange(Self);
end;

procedure TBag.SetLocking(Updating: Boolean);
begin
  if Updating then { Bag has become unlocked }
    Change;
end;

Since this mechanism can be used in many situations, the lock patterns let’s you edit the patterns field and method names. This pattern is one of the few patterns that can be applied to the same class more than once and still be meaningful. You would of course need different names, such as LockUpdate, LockScreenUpdate or BeginUpdate.