Delphi - есть ли какой-нибудь эквивалент C# блокировки?

Я пишу многопоточное приложение на Delphi, и мне нужно что-то использовать для защиты общих ресурсов.

В C# я бы использовал ключевое слово "lock":

private someMethod() {
    lock(mySharedObj) {
        //...do something with mySharedObj
    }
}

В Delphi я не смог найти ничего подобного, я нашел только метод TThread.Synchronize(someMethod), который предотвращает потенциальные конфликты, вызывая someMethod в основном потоке VCL, но это не совсем то, что я хочу сделать....

Редактировать: я использую Delphi 6

5 ответов

Решение

(Не), к счастью, вы не можете заблокировать произвольные объекты в Delphi 6 (хотя вы можете сделать это в более поздних версиях, 2009 и более поздних), поэтому вам нужно иметь отдельный объект блокировки, обычно критическую секцию.

TCriticalSection (примечание: документация от FreePascal, но она существует и в Delphi):

Пример кода:

type
  TSomeClass = class
  private
    FLock : TCriticalSection;
  public
    constructor Create();
    destructor Destroy; override;

    procedure SomeMethod;
  end;

constructor TSomeClass.Create;
begin
  FLock := TCriticalSection.Create;
end;

destructor TSomeClass.Destroy;
begin
  FreeAndNil(FLock);
end;

procedure TSomeClass.SomeMethod;
begin
  FLock.Acquire;
  try
    //...do something with mySharedObj
  finally
    FLock.Release;
  end;
end;

В Delphi 6 нет эквивалента. Начиная с Delphi 2009, вы можете использовать System.TMonitor методы захвата замков на произвольных объектах.

System.TMonitor.Enter(obj);
try
  // ...
finally
  System.TMonitor.Exit(obj);
end;

(Вам нужен префикс "System", потому что имя TMonitor конфликтует с типом в модуле Forms. Альтернативой является использование глобального MonitorEnter а также MonitorExit функции.)

Хотя это не так просто, как в C#, вам может помочь следующее.

  with Lock(mySharedObj) do
  begin
    //...do something with mySharedObj
    UnLock;
  end;

В двух словах

  • список хранится для каждого экземпляра, который вы хотите защитить.
  • когда вторая нить вызывает Lock(mySharedObj), во внутреннем списке будет выполнен поиск существующей блокировки. Новая блокировка будет создана, если существующая блокировка не найдена. Новый поток будет заблокирован, если другой поток все еще имеет блокировку.
  • Unlock необходимо, потому что мы не можем быть уверены, что ссылка на экземпляр ILock только выйдет из области видимости в конце вызова метода Lock, (Если бы мы могли, Unlock может быть удален).

Обратите внимание, что в этом проекте один TLock создается для каждого экземпляра объекта, который вы хотите защитить, без его освобождения до завершения работы приложения.
Это может быть учтено, но это будет связано с возни с _AddRef & _Release.


unit uLock;

interface

type
  ILock = interface
    ['{55C05EA7-D22E-49CF-A337-9F989006D630}']
    procedure UnLock;
  end;

function Lock(const ASharedObj: TObject): ILock;

implementation

uses
  syncobjs, classes;

type
  _ILock = interface
    ['{BAC7CDD2-0660-4375-B673-ECFA2BA0B888}']
    function SharedObj: TObject;
    procedure Lock;
  end;

  TLock = class(TInterfacedObject, ILock, _ILock)
  private
    FCriticalSection: TCriticalSection;
    FSharedObj: TObject;
    function SharedObj: TObject;
  public
    constructor Create(const ASharedObj: TObject);
    destructor Destroy; override;
    procedure Lock;
    procedure UnLock;
  end;

var
  Locks: IInterfaceList;
  InternalLock: TCriticalSection;

function Lock(const ASharedObj: TObject): ILock;
var
  I: Integer;
begin
  InternalLock.Acquire;
  try
    //***** Does a lock exists for given Shared object
    for I := 0 to Pred(Locks.Count) do
      if (Locks[I] as _ILock).SharedObj = ASharedObj then
      begin
        Result := ILock(Locks[I]);
        Break;
      end;

    //***** Create and add a new lock for the shared object
    if not Assigned(Result) then
    begin
      Result := TLock.Create(ASharedObj);
      Locks.Add(Result);
    end;
  finally
    InternalLock.Release;
  end;
  (Result as _ILock).Lock;
end;

{ TLock }

constructor TLock.Create(const ASharedObj: TObject);
begin
  inherited Create;
  FSharedObj := ASharedObj;
  FCriticalSection := TCriticalSection.Create;
end;

destructor TLock.Destroy;
begin
  FCriticalSection.Free;
  inherited Destroy;
end;

procedure TLock.Lock;
begin
  FCriticalSection.Acquire;
end;

function TLock.SharedObj: TObject;
begin
  Result := FSharedObj;
end;

procedure TLock.UnLock;
begin
  FCriticalSection.Release;
end;

initialization
  Locks := TInterfaceList.Create;
  InternalLock := TCriticalSection.Create;

finalization
  InternalLock.Free;
  Locks := nil

end.

Как уже говорилось, для короткого кода, который не вызывает вне локальной области и не получает никаких других блокировок, вы можете использовать критические секции через SyncObjs.TCriticalSection,
для более длинного / более сложного кода вы можете использовать SyncObjs.TMutex, который является ожидаемым (с тайм-аутом), не останавливается, если владелец потока умирает, и может быть разделен по имени с другими процессами.
Использование этих оболочек облегчает изменения уровня синхронизации.

Во всех случаях остерегайтесь драконов здесь: мой ответ на разницу между функцией WaitFor для TMutex delphi и аналогом в Win32 API

Используя помощников класса вы можете использовать это. Не будет работать со старыми версиями, хотя. Но я бы посоветовал использовать TMonitor только в XE5. Так как это намного медленнее, чем TRTLCriticalSection.

http://www.delphitools.info/2013/06/06/tmonitor-vs-trtlcriticalsection/

THelper = class helper for TObject
  procedure Lock;
  procedure Unlock;
end;

procedure THelper.Lock;
begin
  System.TMonitor.Enter(TObject(Self));
end;

procedure THelper.Unlock;
begin
  System.TMonitor.Exit(TObject(Self));
end;
Другие вопросы по тегам